This commit is contained in:
wangyidong
2023-05-08 16:28:40 +08:00
138 changed files with 4191 additions and 1968 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
FROM hub.qucheng.com/ci/zentao-ztf:php8-2023042805
FROM hub.qucheng.com/ci/zentao-ztf:php8-2023050806
# Install zentao
ARG VERSION
Vendored
+5 -2
View File
@@ -33,8 +33,10 @@ pipeline {
steps {
container('sonar') {
withSonarQubeEnv('sonarqube') {
sh 'git config --global --add safe.directory $(pwd)'
sh 'sonar-scanner -Dsonar.analysis.user=$(git show -s --format=%an)'
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
sh 'git config --global --add safe.directory $(pwd)'
sh 'sonar-scanner -Dsonar.analysis.user=$(git show -s --format=%an)'
}
}
}
}
@@ -138,6 +140,7 @@ pipeline {
container('zentao') {
sh 'initdb.php config'
sh '/apps/zentao/test/ztest extract ; /apps/zentao/test/ztest ${SEQUENCE} | tee /apps/zentao/test/${SEQUENCE}.log'
sh 'ls /apps/zentao/www ; cat /apps/zentao/www/coverage.php'
sh 'pipeline-unittest.sh /apps/zentao/test/${SEQUENCE}.log'
}
}
+11 -3
View File
@@ -1,6 +1,7 @@
VERSION = $(shell head -n 1 VERSION)
XUANVERSION = $(shell head -n 1 extension/xuanxuan/XUANVERSION)
XVERSION = $(shell head -n 1 extension/xuanxuan/XVERSION)
VERSION = $(shell head -n 1 VERSION)
XUANVERSION = $(shell head -n 1 extension/xuanxuan/XUANVERSION)
XVERSION = $(shell head -n 1 extension/xuanxuan/XVERSION)
XHPROF_VERSION = 2.3.9
XUANPATH := $(XUANXUAN_SRC_PATH)
BUILD_PATH := $(if $(ZENTAO_BUILD_PATH),$(ZENTAO_BUILD_PATH),$(shell pwd))
@@ -345,3 +346,10 @@ ci:
mv ZenTaoALM.$(VERSION).int.php*.zip ZenTaoPMS.$(VERSION).php*.zip $(RELEASE_PATH)/pmsPack
mv *.deb $(RELEASE_PATH)/pmsPack/deb/
mv *.rpm $(RELEASE_PATH)/pmsPack/rpm/
xhprof:
wget https://pecl.php.net/get/xhprof-$(XHPROF_VERSION).tgz
tar -zxvf xhprof-$(XHPROF_VERSION).tgz
rm -rf www/xhprof/xhprof_html www/xhprof/xhprof_lib
mkdir -p www/xhprof
mv xhprof-$(XHPROF_VERSION)/xhprof_html xhprof-$(XHPROF_VERSION)/xhprof_lib www/xhprof/
rm -rf xhprof-$(XHPROF_VERSION)* package.xml
+4
View File
@@ -0,0 +1,4 @@
ALTER TABLE `zt_project` ADD `stageBy` enum('project', 'product') NOT NULL DEFAULT 'product' AFTER `division`;
UPDATE `zt_project` SET `stageBy` = 'project' WHERE `division` = '0';
UPDATE `zt_project` SET `stageBy` = 'product' WHERE `division` = '1';
ALTER TABLE `zt_project` DROP `division`;
+1 -1
View File
@@ -1354,7 +1354,7 @@ CREATE TABLE IF NOT EXISTS `zt_project` (
`whitelist` text NULL,
`order` mediumint(8) unsigned NOT NULL DEFAULT '0',
`vision` varchar(10) NOT NULL DEFAULT 'rnd',
`division` enum('0','1') NOT NULL DEFAULT '1',
`stageBy` enum('project','product') NOT NULL DEFAULT 'product',
`displayCards` smallint(6) NOT NULL default '0',
`fluidBoard` enum('0','1') NOT NULL DEFAULT '0',
`multiple` enum('0','1') NOT NULL DEFAULT '1',
+5 -9
View File
@@ -750,16 +750,12 @@ class baseRouter
$outputDir = ini_get('xhprof.output_dir');
if(!is_dir($xhprofPath)) return false;
if(!$outputDir) $outputDir = $xhprofPath . DS . 'xhprof_runs';
if(!is_dir($outputDir)) mkdir($outputDir, 0777, true);
include_once $libUtilsPath . 'xhprof_lib.php';
include_once $libUtilsPath . 'xhprof_runs.php';
if(!$outputDir)
{
$outputDir = $xhprofPath . DS . 'xhprof_runs';
if(!is_dir($outputDir)) mkdir($outputDir, 0777, true);
}
$xhprofRuns = new \XHProfRuns_Default($outputDir);
$type = "{$this->moduleName}_{$this->methodName}";
$runID = $xhprofRuns->save_run($log, $type);
@@ -2042,7 +2038,7 @@ class baseRouter
$fileName = baseName((string) $apiFile);
[$method] = explode('.', $fileName);
$url = self::extractAPIURL($apiFile);
$url = static::extractAPIURL($apiFile);
if($url) $hookCodes[$method][] = "return helper::requestAPI('$url');";
}
foreach($hookFiles as $hookFile)
@@ -2792,7 +2788,7 @@ class baseRouter
public function connectByPDO(object $params): object|bool
{
$dsn = null;
if(!isset($params->driver)) self::triggerError('no pdo driver defined, it should be mysql or sqlite', __FILE__, __LINE__, $exit = true);
if(!isset($params->driver)) static::triggerError('no pdo driver defined, it should be mysql or sqlite', __FILE__, __LINE__, $exit = true);
if(!isset($params->user)) return false;
try
{
@@ -2827,7 +2823,7 @@ class baseRouter
header("location: {$this->config->webRoot}checktable.php");
exit;
}
self::triggerError($message, __FILE__, __LINE__, $exit = true);
static::triggerError($message, __FILE__, __LINE__, $exit = true);
}
}
+8
View File
@@ -22,6 +22,14 @@ class form extends baseFixer
*/
public $rawdata;
/**
* 原始配置。
* The raw cofig.
*
* @var array
*/
public $rawconfig;
/**
* 错误信息列表。
* Error list.
+1 -1
View File
@@ -427,7 +427,7 @@ class js extends baseJS
if($full)
{
$document = "<html><meta charset='utf-8'/><style>body{background:white}</style><script>";
if(strpos($_SERVER['HTTP_USER_AGENT'], 'xuanxuan') != false)
if(isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'xuanxuan') != false)
{
/* Inject handler for confirm, prompt and alert. */
$document .= <<<EOT
+1 -1
View File
@@ -64,7 +64,7 @@ class wg
{
$this->props = new props();
$this->gid = self::nextGid();
$this->gid = static::nextGid();
$this->setDefaultProps(static::getDefaultProps());
$this->add(func_get_args());
$this->created();
+6 -6
View File
@@ -25,28 +25,28 @@ class zin
public static function getData($namePath, $defaultValue = NULL)
{
return \zin\utils\deepGet(self::$data, $namePath, $defaultValue);
return \zin\utils\deepGet(static::$data, $namePath, $defaultValue);
}
public static function setData($namePath, $value)
{
\zin\utils\deepSet(self::$data, $namePath, $value);
\zin\utils\deepSet(static::$data, $namePath, $value);
}
public static function enableGlobalRender()
{
self::$enabledGlobalRender = true;
static::$enabledGlobalRender = true;
}
public static function disableGlobalRender()
{
self::$enabledGlobalRender = false;
static::$enabledGlobalRender = false;
}
public static function renderInGlobal()
{
if(!self::$enabledGlobalRender) return false;
if(!static::$enabledGlobalRender) return false;
self::$globalRenderList = array_merge(self::$globalRenderList, func_get_args());
static::$globalRenderList = array_merge(static::$globalRenderList, func_get_args());
}
}
+10 -6
View File
@@ -5,6 +5,7 @@ class btn extends wg
{
static $defineProps = array(
'icon?:string',
'iconClass?:string',
'text?:string',
'square?:bool',
'disabled?:bool',
@@ -13,6 +14,7 @@ class btn extends wg
'target?:string',
'size?:string|int',
'trailingIcon?:string',
'trailingIconClass?:string',
'caret?:string|bool',
'hint?:string',
'type?:string',
@@ -54,16 +56,18 @@ class btn extends wg
private function getChildren()
{
$caret = $this->prop('caret');
$text = $this->prop('text');
$icon = $this->prop('icon');
$trailingIcon = $this->prop('trailingIcon');
$caret = $this->prop('caret');
$text = $this->prop('text');
$icon = $this->prop('icon');
$iconClass = $this->prop('iconClass');
$trailingIcon = $this->prop('trailingIcon');
$trailingIconClass = $this->prop('trailingIconClass');
$children = array();
if(!empty($icon)) $children[] = icon($icon);
if(!empty($icon)) $children[] = icon($icon, setClass($iconClass));
if(!empty($text)) $children[] = h::span($text, setClass('text'));
$children[] = parent::build();
if(!empty($trailingIcon)) $children[] = icon($trailingIcon);
if(!empty($trailingIcon)) $children[] = icon($trailingIcon, setClass($trailingIconClass));
if(!empty($caret)) $children[] = h::span(setClass(is_string($caret) ? "caret-$caret" : 'caret'));
return $children;
+6 -7
View File
@@ -115,7 +115,6 @@ class block extends control
{
$blockID = (int)$blockID;
if($type == 'delete') $this->block->deleteBlock($blockID);
if($type == 'hidden') $this->block->hidden($blockID);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$this->loadModel('score')->create('block', 'set');
@@ -284,17 +283,17 @@ class block extends control
if(empty($block)) return $html;
if(isset($block->params->num) and !isset($block->params->count)) $block->params->count = $block->params->num;
$code = $block->code;
if($code == 'statistic' or $code == 'list' or $code == 'overview') $code = $block->module . ucfirst($code);
$function = 'print' . ucfirst($code) . 'Block';
if(method_exists('blockZen', $function)) $this->blockZen->$function($blockID);
if(method_exists('blockZen', $function)) $this->blockZen->$function($block);
$params = $block->params;
if(isset($params->num) and !isset($params->count)) $params->count = $params->num;
if(!$this->selfCall)
{
$this->app->user = $this->dao->select('*')->from(TABLE_USER)->where('ranzhi')->eq($params->account)->fetch();
$this->app->user = $this->dao->select('*')->from(TABLE_USER)->where('ranzhi')->eq($block->params->account)->fetch();
if(empty($this->app->user))
{
$this->app->user = new stdclass();
@@ -319,12 +318,12 @@ class block extends control
$moreLink = '';
if(isset($this->config->block->modules[$module]->moreLinkList->{$code}))
{
list($moduleName, $method, $vars) = explode('|', sprintf($this->config->block->modules[$module]->moreLinkList->{$code}, isset($params->type) ? $params->type : ''));
list($moduleName, $method, $vars) = explode('|', sprintf($this->config->block->modules[$module]->moreLinkList->{$code}, isset($block->params->type) ? $block->params->type : ''));
$this->view->moreLink = $this->createLink($moduleName, $method, $vars);
}
$this->view->moreLink = $moreLink;
$viewType = (isset($params->viewType) and $params->viewType == 'json') ? 'json' : 'html';
$viewType = (isset($block->params->viewType) and $block->params->viewType == 'json') ? 'json' : 'html';
if($viewType == 'json')
{
unset($this->view->app);
+100
View File
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
/**
* The product list block view file of block module of ZenTaoPMS.
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author LiuRuoGu <liuruogu@easycorp.ltd>
* @package block
* @link https://www.zentao.net
*/
namespace zin;
$cols = array
(
array
(
'name' => 'name',
'title' => '产品名称',
'width' => '200',
'type' => 'link',
'sortType' => 1,
),
array
(
'name' => 'POName',
'title' => '负责人',
'width' => 200,
'type' => 'avatarBtn',
'sortType' => 1,
),
array
(
'name' => 'TODO:zu1',
'title' => '未关闭反馈',
'width' => 100,
'sortType' => 1,
),
array
(
'name' => 'TODO:zu1',
'title' => '激活需求',
'width' => 100,
'sortType' => 1,
),
array
(
'name' => 'TODO:zu1',
'title' => '需求完成率',
'width' => 100,
'sortType' => 1,
),
array
(
'name' => 'TODO:zu1',
'title' => '计划',
'width' => 100,
'sortType' => 1,
),
array
(
'name' => 'TODO:zu1',
'title' => '激活BUG',
'width' => 100,
'sortType' => 1,
),
array
(
'name' => 'TODO:zu1',
'title' => '发布',
'width' => 100,
'sortType' => 1,
),
);
$data = array();
foreach($productStats as $product)
{
if(!empty($product->PO))
{
$product->POName = zget($users, $product->PO);
$product->POAvatar = $usersAvatar[$product->PO];
$product->POAccount = $product->PO;
}
}
$table = array();
panel
(
dtable
(
setClass('shadow rounded'),
set::cols($cols),
set::data($productStats)
)
);
render();
+298 -231
View File
@@ -11,275 +11,342 @@ declare(strict_types=1);
namespace zin;
$isChineseLang = in_array($this->app->getClientLang(), array('zh-cn','zh-tw'));
$blockNavID = 'nav-' . uniqid();
$selected = key($projects);
$navTabs = array();
/* 展示左侧的项目列表。 */
foreach($projects as $project)
/**
* 获取区块左侧的项目列表.
* Get project tabs on the left side.
*
* @param array $projects
* @param string $blockNavCode
* @access public
* @return array
*/
function getProjectTabs($projects, $blockNavCode): array
{
$navTabs[] = li
(
set('class', 'nav-item' . ($project->id == $selected ? ' active' : '')),
a
$navTabs = array();
$selected = key($projects);
foreach($projects as $project)
{
$navTabs[] = li
(
set('class', 'ellipsis'),
set('data-toggle', 'tab'),
set('href', "#tab3{$blockNavID}Content{$project->id}"),
$project->name
),
a
(
set('class', 'link flex-1 text-right hidden'),
set('href', helper::createLink('project', 'index', "projectID=$project->id")),
icon
set('class', 'nav-item' . ($project->id == $selected ? ' active' : '')),
a
(
set('class', 'rotate-90 text-primary'),
'export'
set('class', 'ellipsis'),
set('data-toggle', 'tab'),
set('href', "#tab3{$blockNavCode}Content{$project->id}"),
$project->name
),
a
(
set('class', 'link flex-1 text-right hidden'),
set('href', helper::createLink('project', 'index', "projectID=$project->id")),
icon
(
set('class', 'rotate-90 text-primary'),
'export'
)
)
)
);
);
}
return $navTabs;
}
$tabItems = array();
foreach($projects as $project)
/**
* 获取区块右侧显示的项目信息.
* Get project statistical information.
*
* @param object $projects
* @param string $blockNavID
* @access public
* @return array
*/
function getProjectInfo($projects, $blockNavID): array
{
if(in_array($project->model, array('scrum', 'kanban', 'agileplus')))
$selected = key($projects);
$tabItems = array();
foreach($projects as $project)
{
/* 展示右侧的项目统计项。 */
$cells = array();
foreach($config->block->projectstatistic->dtable as $module => $items)
{
$cellItems = array();
foreach($items as $item)
{
$field = $item['field'];
$unit = $item['unit'];
$cellItems[] = div
(
set('class', 'flex py-4'),
cell
(
set('width', '50%'),
set('class', 'text-right text-gray'),
span($lang->block->projectstatistic->{$field} . ' :')
),
cell
(
set('width', '50%'),
set('class', 'text-left'),
span
(
set('class', 'font-bold text-black'),
zget($project, $field, 0)
),
span($lang->block->projectstatistic->{$unit})
)
);
}
$cells[] = cell
$tabItems[] = div
(
set('class', 'tab-pane' . ($project->id == $selected ? ' active' : '')),
set('id', "tab3{$blockNavID}Content{$project->id}"),
in_array($project->model, array('scrum', 'kanban', 'agileplus')) ? getScrumProjectInfo($project) : getWaterfallProjectInfo($project)
);
}
return $tabItems;
}
/**
* 获取敏捷类项目的统计信息.
* Get scrum project info.
*
* @param object $project
* @access public
* @return void
*/
function getScrumProjectInfo($project)
{
global $lang;
$scrumProjectInfo = div
(
/* 区块右侧顶部的项目概况。 */
div
(
set('class', 'flex bg-white h-10 leading-9 px-4 shadow-sm'),
cell
(
set('class', 'flex-1 px-2 py-4'),
div
set('class', 'text-left mr-6'),
span
(
set('class', 'px-2'),
set('class', 'text-gray'),
'距离项目结束还剩',
span
(
set('class', 'font-bold'),
$lang->block->projectstatistic->{$module}
set('class', 'font-bold text-black px-1'),
zget($project, 'remainingDays' , 0)
),
$lang->block->projectstatistic->day
)
),
cell
(
set('class', 'flex-1 text-left'),
span
(
set('class', 'text-gray mr-5'),
'存在风险 : ',
span
(
set('class', 'font-bold text-warning'),
'3'
)
),
$cellItems
span
(
set('class', 'text-gray'),
'存在问题 : ',
span
(
set('class', 'font-bold text-warning'),
'1'
)
)
),
(!empty($project->executions) and $project->multiple) ? cell
(
/* 项目最近的一次执行。 */
set('class', 'flex-1 text-right'),
span
(
set('class', 'text-gray'),
'最近执行 ',
a
(
set('href', helper::createLink('execution', 'task', "executionID={$project->executions[0]->id}")),
set('title', $project->executions[0]->name),
$project->executions[0]->name,
)
)
) : null
),
div
(
/* 区块右侧主体显示的项目统计项。 */
set('class', 'flex'),
getProjectStatisticItems($project)
)
);
return $scrumProjectInfo;
}
/**
* 获取项目的统计项.
* get project statistic items.
*
* @param object $project
* @access public
* @return array
*/
function getProjectStatisticItems($project): array
{
global $config, $lang;
$cells = array();
foreach($config->block->projectstatistic->dtable as $module => $items)
{
$cellItems = array();
foreach($items as $item)
{
$field = $item['field'];
$unit = $item['unit'];
$cellItems[] = div
(
set('class', 'flex py-4'),
cell
(
set('width', '50%'),
set('class', 'text-right text-gray'),
span($lang->block->projectstatistic->{$field} . ' :')
),
cell
(
set('width', '50%'),
set('class', 'text-left'),
span
(
set('class', 'font-bold text-black'),
zget($project, $field, 0)
),
span($lang->block->projectstatistic->{$unit})
)
);
}
$cells[] = cell
(
set('class', 'flex-1 px-2 py-4'),
div
(
set('class', 'px-2'),
span
(
set('class', 'font-bold'),
$lang->block->projectstatistic->{$module}
),
),
$cellItems
);
}
/* 展示右侧顶部的项目状况。 */
$tabItems[] = div
(
set('class', 'tab-pane' . ($project->id == $selected ? ' active' : '')),
set('id', "tab3{$blockNavID}Content{$project->id}"),
return $cells;
}
in_array($project->model, array('scrum', 'kanban', 'agileplus')) ? div
/**
* 获取瀑布类项目的统计信息.
* get waterfall project info.
*
* @param object $project
* @access public
* @return void
*/
function getWaterfallProjectInfo($project)
{
global $app, $lang;
$isChineseLang = in_array($app->getClientLang(), array('zh-cn','zh-tw'));
$waterfallProjectInfo = div
(
/* 瀑布项目展示概况。 */
set('class', 'weekly-row'),
div
(
/* 敏捷、看板等项目展示概况。 */
div
span
(
set('class', 'flex bg-white h-10 leading-9 px-4 shadow-sm'),
cell
(
set('class', 'text-left mr-6'),
span
(
set('class', 'text-gray'),
'距离项目结束还剩',
span
(
set('class', 'font-bold text-black px-1'),
zget($project, 'remainingDays' , 0)
),
$lang->block->projectstatistic->day
)
),
cell
(
set('class', 'flex-1 text-left'),
span
(
set('class', 'text-gray mr-5'),
'存在风险 : ',
span
(
set('class', 'font-bold text-warning'),
'3'
)
),
span
(
set('class', 'text-gray'),
'存在问题 : ',
span
(
set('class', 'font-bold text-warning'),
'1'
)
)
),
(!empty($project->executions) and $project->multiple) ? cell
(
set('class', 'flex-1 text-right'),
span
(
set('class', 'text-gray'),
'最近执行 ',
a
(
set('href', $this->createLink('execution', 'task', "executionID={$project->executions[0]->id}")),
set('title', $project->executions[0]->name),
$project->executions[0]->name,
)
)
) : null
set('class', 'weekly-title'),
$lang->project->weekly
),
div
span
(
set('class', 'flex'),
$cells
set('class', 'weekly-stage'),
$project->current
)
) : div
),
div
(
/* 瀑布项目展示概况。 */
set('class', 'weekly-row'),
set('class', 'flex'),
div
(
span
(
set('class', 'weekly-title'),
$lang->project->weekly
),
span
(
set('class', 'weekly-stage'),
$project->current
)
),
div
(
set('class', 'flex'),
set('class', 'flex-1'),
div
(
set('class', 'flex-1'),
set('class', 'progress'),
span
(
set('class', 'mr-4'),
$lang->project->progress . ' : ' . $project->progress . '%'
),
div
(
set('class', 'progress'),
span
(
set('class', 'mr-4'),
$lang->project->progress . ' : ' . $project->progress . '%'
),
div
(
set('class', 'progress-bar'),
set('role', 'progressbar'),
setStyle(['width' => $project->progress . '%']),
)
set('class', 'progress-bar'),
set('role', 'progressbar'),
setStyle(['width' => $project->progress . '%']),
)
),
div
(
set('class', 'flex-1 text-center'),
$lang->project->teamCount . ' : ' . $project->teamCount
),
div
(
set('class', 'flex-1 text-left'),
$lang->project->budget . ' : ' . ($project->budget != 0 ? $project->budget : $this->lang->project->future)
),
div(set('class', 'flex-1'))
),
div
(
set('class', 'flex'),
div
(
set('class', 'flex-1'),
$isChineseLang ? $lang->project->pv . '(' . $lang->project->pvTitle . ')' : $lang->project->pv
),
div
(
set('class', 'flex-1'),
$isChineseLang ? $lang->project->ev . '(' . $lang->project->evTitle . ')' : $lang->project->ev
),
div
(
set('class', 'flex-1'),
$isChineseLang ? $lang->project->ac . '(' . $lang->project->acTitle . ')' : $lang->project->ac
),
div
(
set('class', 'flex-1'),
$isChineseLang ? $lang->project->sv . '(' . $lang->project->svTitle . ')' : $lang->project->sv
),
div
(
set('class', 'flex-1'),
$isChineseLang ? $lang->project->cv . '(' . $lang->project->cvTitle . ')' : $lang->project->cv
)
),
div
(
set('class', 'flex'),
div
(
set('class', 'flex-1'),
$project->pv
),
div
(
set('class', 'flex-1'),
$project->ev
),
div
(
set('class', 'flex-1'),
$project->ac
),
div
(
set('class', 'flex-1'),
$project->sv
),
div
(
set('class', 'flex-1'),
$project->cv
)
set('class', 'flex-1 text-center'),
$lang->project->teamCount . ' : ' . $project->teamCount
),
div
(
set('class', 'flex-1 text-left'),
$lang->project->budget . ' : ' . ($project->budget != 0 ? $project->budget : $lang->project->future)
),
div(set('class', 'flex-1'))
),
div
(
set('class', 'flex'),
div
(
set('class', 'flex-1'),
$isChineseLang ? $lang->project->pv . '(' . $lang->project->pvTitle . ')' : $lang->project->pv
),
div
(
set('class', 'flex-1'),
$isChineseLang ? $lang->project->ev . '(' . $lang->project->evTitle . ')' : $lang->project->ev
),
div
(
set('class', 'flex-1'),
$isChineseLang ? $lang->project->ac . '(' . $lang->project->acTitle . ')' : $lang->project->ac
),
div
(
set('class', 'flex-1'),
$isChineseLang ? $lang->project->sv . '(' . $lang->project->svTitle . ')' : $lang->project->sv
),
div
(
set('class', 'flex-1'),
$isChineseLang ? $lang->project->cv . '(' . $lang->project->cvTitle . ')' : $lang->project->cv
)
),
div
(
set('class', 'flex'),
div
(
set('class', 'flex-1'),
$project->pv
),
div
(
set('class', 'flex-1'),
$project->ev
),
div
(
set('class', 'flex-1'),
$project->ac
),
div
(
set('class', 'flex-1'),
$project->sv
),
div
(
set('class', 'flex-1'),
$project->cv
)
)
);
return $waterfallProjectInfo;
}
$blockNavCode = 'nav-' . uniqid();
div
(
set('class', 'projectstatistic-block'),
@@ -293,14 +360,14 @@ div
ul
(
set('class', 'nav nav-tabs nav-stacked'),
$navTabs,
getProjectTabs($projects, $blockNavCode)
),
),
cell
(
set('class', 'tab-content'),
set('width', '75%'),
$tabItems
getProjectInfo($projects, $blockNavCode)
)
)
);
+27
View File
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/**
* The todo list block view file of block module of ZenTaoPMS.
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author LiuRuoGu <liuruogu@easycorp.ltd>
* @package block
* @link https://www.zentao.net
*/
namespace zin;
panel
(
div
(
set('class', 'flex'),
btn
(
set('class', 'btn-info'),
'添加待办'
)
)
);
render();
+49 -43
View File
@@ -386,7 +386,7 @@ class blockZen extends block
* @access public
* @return void
*/
public function printTodoBlock()
public function printTodoListBlock()
{
$limit = ($this->viewType == 'json' or !isset($this->params->count)) ? 0 : (int)$this->params->count;
$todos = $this->loadModel('todo')->getList('all', $this->app->user->account, 'wait, doing', $limit, null, 'date, begin');
@@ -398,7 +398,7 @@ class blockZen extends block
$this->session->set('storyList', $uri, 'product');
$this->session->set('testtaskList', $uri, 'qa');
$todos = $this->blockZen->unsetTodos($todos);
$todos = $this->unsetTodos($todos);
$this->view->todos = $todos;
}
@@ -627,45 +627,53 @@ class blockZen extends block
* @access public
* @return void
*/
public function printProductBlock()
public function printProductListBlock()
{
$this->app->loadClass('pager', true);
if(!empty($this->params->type) and preg_match('/[^a-zA-Z0-9_]/', $this->params->type)) return;
$count = isset($this->params->count) ? (int)$this->params->count : 0;
$type = isset($this->params->type) ? $this->params->type : '';
$pager = pager::init(0, $count , 1);
/* TODO:zu1 */
//$this->app->loadClass('pager', true);
//if(!empty($this->params->type) and preg_match('/[^a-zA-Z0-9_]/', $this->params->type)) return;
//$count = isset($this->params->count) ? (int)$this->params->count : 0;
//$type = isset($this->params->type) ? $this->params->type : '';
//$pager = pager::init(0, $count , 1);
$productStats = $this->loadModel('product')->getStats('order_desc', $this->viewType != 'json' ? $pager : '', $type);
$productIdList = array();
foreach($productStats as $product) $productIdList[] = $product->id;
//$productStats = $this->loadModel('product')->getStats('order_desc', $this->viewType != 'json' ? $pager : '', $type);
//$productIdList = array();
//foreach($productStats as $product) $productIdList[] = $product->id;
$this->app->loadLang('project');
$executions = $this->dao->select('t1.product,t2.id,t2.project,t2.name,t2.multiple')->from(TABLE_PROJECTPRODUCT)->alias('t1')
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
->where('t1.product')->in($productIdList)
->andWhere('t2.type')->in('stage,sprint')
->andWhere('t2.deleted')->eq(0)
->orderBy('t1.project')
->fetchAll('product');
//$this->app->loadLang('project');
$executionPairs = array();
$noMultiples = array();
foreach($executions as $execution)
{
if(empty($execution->multiple)) $noMultiples[$execution->product] = $execution->project;
$executionPairs[$execution->product] = $execution->name;
}
if($noMultiples)
{
$noMultipleProjects = $this->dao->select('id,name')->from(TABLE_PROJECT)->where('id')->in($noMultiples)->fetchPairs('id', 'name');
foreach($noMultiples as $productID => $projectID)
{
if(isset($noMultipleProjects[$projectID])) $executionPairs[$productID] = $noMultipleProjects[$projectID] . "({$this->lang->project->disableExecution})";
}
}
//$executions = $this->dao->select('t1.product,t2.id,t2.project,t2.name,t2.multiple')->from(TABLE_PROJECTPRODUCT)->alias('t1')
// ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
// ->where('t1.product')->in($productIdList)
// ->andWhere('t2.type')->in('stage,sprint')
// ->andWhere('t2.deleted')->eq(0)
// ->orderBy('t1.project')
// ->fetchAll('product');
//$executionPairs = array();
//$noMultiples = array();
//foreach($executions as $execution)
//{
// if(empty($execution->multiple)) $noMultiples[$execution->product] = $execution->project;
// $executionPairs[$execution->product] = $execution->name;
//}
//if($noMultiples)
//{
// $noMultipleProjects = $this->dao->select('id,name')->from(TABLE_PROJECT)->where('id')->in($noMultiples)->fetchPairs('id', 'name');
// foreach($noMultiples as $productID => $projectID)
// {
// if(isset($noMultipleProjects[$projectID])) $executionPairs[$productID] = $noMultipleProjects[$projectID] . "({$this->lang->project->disableExecution})";
// }
//}
//$this->view->executions = $executionPairs;
$productStats = $this->dao->select('*')->from(TABLE_PRODUCT)->fetchAll();
$this->view->executions = $executionPairs;
$this->view->productStats = $productStats;
$this->view->users = $this->loadModel('user')->getPairs('noletter');
$this->view->userIdPairs = $this->user->getPairs('noletter|showid');
$this->view->usersAvatar = $this->user->getAvatarPairs('');
}
/**
@@ -748,18 +756,16 @@ class blockZen extends block
/**
* Print product statistic block.
*
* @param object $block
* @access public
* @param string $storyType requirement|story
* @return void
*/
public function printProductStatisticBlock($storyType = 'story')
public function printProductStatisticBlock($block)
{
if(!empty($this->params->type) and preg_match('/[^a-zA-Z0-9_]/', $this->params->type)) return;
$status = isset($block->params->type) ? $block->params->type : '';
$count = isset($block->params->count) ? $block->params->count : '';
$status = isset($this->params->type) ? $this->params->type : '';
$count = isset($this->params->count) ? $this->params->count : '';
$products = $this->loadModel('product')->getOrderedProducts($status, $count);
$products = $this->loadModel('product')->getOrderedProducts($status, (int)$count);
$productIdList = array_keys($products);
if(empty($products))
@@ -772,7 +778,7 @@ class blockZen extends block
$stories = $this->dao->select('product, stage, COUNT(status) AS count')->from(TABLE_STORY)
->where('deleted')->eq(0)
->andWhere('product')->in($productIdList)
->beginIF($storyType)->andWhere('type')->eq($storyType)->fi()
->andWhere('type')->eq('story')
->groupBy('product, stage')
->fetchGroup('product', 'stage');
+38 -1
View File
@@ -40,6 +40,43 @@ $config->bug->form->create['status'] = array('required' => false, 'type' => 's
$config->bug->form->create['issueKey'] = array('required' => false, 'type' => 'string', 'default' => '');
$config->bug->form->create['uid'] = array('required' => false, 'type' => 'string', 'default' => '');
$config->bug->form->edit = array();
$config->bug->form->edit['title'] = array('required' => true, 'type' => 'string', 'filter' => 'trim');
$config->bug->form->edit['openedBuild'] = array('required' => true, 'type' => 'array', 'filter' => 'join');
$config->bug->form->edit['product'] = array('required' => false, 'type' => 'int', 'default' => 0);
$config->bug->form->edit['branch'] = array('required' => false, 'type' => 'int', 'default' => 0);
$config->bug->form->edit['project'] = array('required' => false, 'type' => 'int', 'default' => 0);
$config->bug->form->edit['execution'] = array('required' => false, 'type' => 'int', 'default' => 0);
$config->bug->form->edit['plan'] = array('required' => false, 'type' => 'int', 'default' => 0);
$config->bug->form->edit['module'] = array('required' => false, 'type' => 'int', 'default' => 0);
$config->bug->form->edit['story'] = array('required' => false, 'type' => 'int', 'default' => 0);
$config->bug->form->edit['task'] = array('required' => false, 'type' => 'int', 'default' => 0);
$config->bug->form->edit['case'] = array('required' => false, 'type' => 'int', 'default' => 0);
$config->bug->form->edit['testtask'] = array('required' => false, 'type' => 'int', 'default' => 0);
$config->bug->form->edit['duplicateBug'] = array('required' => false, 'type' => 'int', 'default' => 0);
$config->bug->form->edit['severity'] = array('required' => false, 'type' => 'int', 'default' => 3);
$config->bug->form->edit['pri'] = array('required' => false, 'type' => 'int', 'default' => 3);
$config->bug->form->edit['type'] = array('required' => false, 'type' => 'string', 'default' => '');
$config->bug->form->edit['status'] = array('required' => false, 'type' => 'string', 'default' => 'active');
$config->bug->form->edit['keywords'] = array('required' => false, 'type' => 'string', 'default' => '');
$config->bug->form->edit['steps'] = array('required' => false, 'type' => 'string', 'default' => $lang->bug->tplStep . $lang->bug->tplResult . $lang->bug->tplExpect);
$config->bug->form->edit['resolution'] = array('required' => false, 'type' => 'string', 'default' => '');
$config->bug->form->edit['resolvedBuild'] = array('required' => false, 'type' => 'string', 'default' => '');
$config->bug->form->edit['assignedTo'] = array('required' => false, 'type' => 'string', 'default' => '');
$config->bug->form->edit['feedbackBy'] = array('required' => false, 'type' => 'string', 'default' => '');
$config->bug->form->edit['resolvedBy'] = array('required' => false, 'type' => 'string', 'default' => '');
$config->bug->form->edit['closedBy'] = array('required' => false, 'type' => 'string', 'default' => '');
$config->bug->form->edit['notifyEmail'] = array('required' => false, 'type' => 'string', 'default' => '');
$config->bug->form->edit['uid'] = array('required' => false, 'type' => 'string', 'default' => '');
$config->bug->form->edit['os'] = array('required' => false, 'type' => 'array', 'default' => array(''), 'filter' => 'join');
$config->bug->form->edit['browser'] = array('required' => false, 'type' => 'array', 'default' => array(''), 'filter' => 'join');
$config->bug->form->edit['linkBug'] = array('required' => false, 'type' => 'array', 'default' => array(''), 'filter' => 'join');
$config->bug->form->edit['mailto'] = array('required' => false, 'type' => 'array', 'default' => array(''), 'filter' => 'join');
$config->bug->form->edit['deadline'] = array('required' => false, 'type' => 'date', 'default' => NULL);
$config->bug->form->edit['resolvedDate'] = array('required' => false, 'type' => 'date', 'default' => NULL);
$config->bug->form->edit['closedDate'] = array('required' => false, 'type' => 'date', 'default' => NULL);
$config->bug->form->edit['lastEditedDate'] = array('required' => false, 'type' => 'date', 'default' => helper::now());
global $app;
$config->bug->form->close = array();
$config->bug->form->close['status'] = array('required' => false, 'type' => 'string', 'default' => 'closed');
@@ -49,4 +86,4 @@ $config->bug->form->close['lastEditedBy'] = array('required' => false, 'type'
$config->bug->form->close['lastEditedDate'] = array('required' => false, 'type' => 'string', 'default' => helper::now());
$config->bug->form->close['closedBy'] = array('required' => false, 'type' => 'string', 'default' => $app->user->account);
$config->bug->form->close['closedDate'] = array('required' => false, 'type' => 'string', 'default' => helper::now());
$config->bug->form->close['comment'] = array('required' => false, 'type' => 'string', 'default' => '');;
$config->bug->form->close['comment'] = array('required' => false, 'type' => 'string', 'default' => '');
+41 -242
View File
@@ -12,6 +12,7 @@
class bug extends control
{
/**
* 所有产品。
* All products.
*
* @var array
@@ -20,6 +21,7 @@ class bug extends control
public $products = array();
/**
* 当前项目编号。
* Project id.
*
* @var int
@@ -28,16 +30,25 @@ class bug extends control
public $projectID = 0;
/**
* Construct function, load some modules auto.
* 构造函数
* 1.加载其他模块model类。
* 2.获取产品,并输出到视图
* The construct function.
*
* 1. Load model of other modules.
* 2. Get products and assign to view.
*
* @param string $moduleName
* @param string $methodName
* @access public
* @return void
*/
public function __construct($moduleName = '', $methodName = '')
public function __construct(string $moduleName = '', string $methodName = '')
{
parent::__construct($moduleName, $methodName);
$this->loadModel('product');
$this->loadModel('tree');
$this->loadModel('user');
@@ -48,27 +59,28 @@ class bug extends control
/* Get product data. */
$products = array();
$objectID = 0;
$tab = ($this->app->tab == 'project' or $this->app->tab == 'execution') ? $this->app->tab : 'qa';
if(!isonlybody())
{
if($this->app->tab == 'project' or $this->app->tab == 'execution')
$tab = ($this->app->tab == 'project' or $this->app->tab == 'execution') ? $this->app->tab : 'qa';
$mode = (strpos(',create,edit,', ",{$this->app->methodName},") !== false and empty($this->config->CRProduct)) ? 'noclosed' : '';
$objectID = ($tab == 'project' or $tab == 'execution') ? $this->session->{$tab} : 0;
if($tab == 'project' or $tab == 'execution')
{
$objectID = $this->app->tab == 'project' ? $this->session->project : $this->session->execution;
$products = $this->product->getProducts($objectID, 'all', '', false);
$products = $this->product->getProducts($objectID, $mode, $orderBy = '', $withBranch = false);
}
else
{
$mode = ($this->app->methodName == 'create' and empty($this->config->CRProduct)) ? 'noclosed' : '';
$products = $this->product->getPairs($mode, 0, '', 'all');
$products = $this->product->getPairs($mode, $programID = 0, $append = '', $shadow = 'all');
}
if(empty($products) and !helper::isAjaxRequest()) return print($this->locate($this->createLink('product', 'showErrorNone', "moduleName=$tab&activeMenu=bug&objectID=$objectID")));
}
else
{
$mode = (empty($this->config->CRProduct)) ? 'noclosed' : '';
$mode = empty($this->config->CRProduct) ? 'noclosed' : '';
$products = $this->product->getPairs($mode, 0, '', 'all');
}
$this->view->products = $this->products = $products;
}
@@ -645,259 +657,46 @@ class bug extends control
}
/**
* 更新bug信息。
* Edit a bug.
*
* @param int $bugID
* @param bool $comment
* @param string $bugID
* @param bool $comment true|false
* @param string $kanbanGroup
* @access public
* @return void
*/
public function edit($bugID, $comment = false, $kanbanGroup = 'default')
public function edit(string $bugID, bool $comment = false, string $kanbanGroup = 'default')
{
if(!empty($_POST))
{
$oldBug = $this->bug->getByID($bugID);
$formData = form::data($this->config->bug->form->edit);
$bug = $this->bugZen->prepareEditExtras($formData, $oldBug);
if(!$bug) return $this->send($this->bugZen->errorEdit());
$changes = array();
$files = array();
if($comment == false)
if(!$comment)
{
$changes = $this->bug->update($bugID);
if(dao::isError())
{
if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'error', 'message' => dao::getError()));
return print(js::error(dao::getError()));
}
$changes = $this->bug->update($bug, $oldBug);
if(!$changes) return $this->send($this->bugZen->errorEdit());
}
if($this->post->comment != '' or !empty($changes))
{
$action = !empty($changes) ? 'Edited' : 'Commented';
$actionID = $this->action->create('bug', $bugID, $action, $this->post->comment);
$this->action->logHistory($actionID, $changes);
}
if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success', 'data' => $bugID));
$bug = $this->bug->getById($bugID);
$this->bugZen->processAfterEdit($bugID, $this->post->comment, $changes);
$this->executeHooks($bugID);
if($bug->toTask != 0)
{
foreach($changes as $change)
{
if($change['field'] == 'status')
{
$confirmURL = $this->createLink('task', 'view', "taskID=$bug->toTask");
$cancelURL = $this->server->HTTP_REFERER;
return print(js::confirm(sprintf($this->lang->bug->remindTask, $bug->Task), $confirmURL, $cancelURL, 'parent', 'parent'));
}
}
}
if(isonlybody())
{
$execution = $this->loadModel('execution')->getByID($bug->execution);
if($this->app->tab == 'execution')
{
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
if(isset($execution->type) and $execution->type == 'kanban')
{
$rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : '';
$kanbanData = $this->loadModel('kanban')->getRDKanban($bug->execution, $execLaneType, 'id_desc', 0, $kanbanGroup, $rdSearchValue);
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)"));
}
else
{
$taskSearchValue = $this->session->taskSearchValue ? $this->session->taskSearchValue : '';
$kanbanData = $this->loadModel('kanban')->getExecutionKanban($bug->execution, $execLaneType, $execGroupBy, $taskSearchValue);
$kanbanType = $execLaneType == 'all' ? 'bug' : key($kanbanData);
$kanbanData = $kanbanData[$kanbanType];
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban(\"bug\", $kanbanData)"));
}
}
else
{
return print(js::closeModal('parent.parent'));
}
}
return print(js::locate($this->createLink('bug', 'view', "bugID=$bugID"), 'parent'));
return $this->send($this->bugZen->responseAfterEdit($bugID, $changes, $kanbanGroup));
}
/* Get the info of bug, current product and modue. */
$bug = $this->bug->getById($bugID);
$productID = $bug->product;
$executionID = $bug->execution;
$projectID = $bug->project;
$currentModuleID = $bug->module;
$product = $this->loadModel('product')->getByID($productID);
$execution = $this->loadModel('execution')->getByID($executionID);
$bug = $this->bug->getByID($bugID);
$this->bug->checkBugExecutionPriv($bug);
if(!isset($this->products[$bug->product]))
{
$this->products[$bug->product] = $product->name;
$this->view->products = $this->products;
}
$this->bugZen->setEditMenu($bug);
/* Set the menu. */
if($this->app->tab == 'project') $this->loadModel('project')->setMenu($bug->project);
if($this->app->tab == 'execution') $this->loadModel('execution')->setMenu($bug->execution);
if($this->app->tab == 'qa') $this->qa->setMenu($this->products, $productID, $bug->branch);
if($this->app->tab == 'devops')
{
session_write_close();
$repos = $this->loadModel('repo')->getRepoPairs('project', $bug->project);
$this->repo->setMenu($repos);
$this->lang->navGroup->bug = 'devops';
}
/* Unset discarded types. */
foreach($this->config->bug->discardedTypes as $type)
{
if($bug->type != $type) unset($this->lang->bug->typeList[$type]);
}
if($this->app->tab == 'qa')
{
$this->view->products = $this->config->CRProduct ? $this->products : $this->product->getPairs('noclosed');
}
if($this->app->tab == 'project')
{
$products = array();
$productList = $this->config->CRProduct ? $this->product->getOrderedProducts('all', 40, $bug->project) : $this->product->getOrderedProducts('normal', 40, $bug->project);
foreach($productList as $productInfo) $products[$productInfo->id] = $productInfo->name;
$this->view->products = $products;
}
/* Set header and position. */
$this->view->title = $this->lang->bug->edit . "BUG #$bug->id $bug->title - " . $this->products[$productID];
$this->view->position[] = html::a($this->createLink('bug', 'browse', "productID=$productID"), $this->products[$productID]);
$this->view->position[] = $this->lang->bug->edit;
/* Assign. */
$allBuilds = $this->loadModel('build')->getBuildPairs($productID, 'all', 'noempty');
if($executionID)
{
$openedBuilds = $this->build->getBuildPairs($productID, $bug->branch, 'noempty,noterminate,nodone,withbranch,noreleased', $executionID, 'execution');
}
elseif($projectID)
{
$openedBuilds = $this->build->getBuildPairs($productID, $bug->branch, 'noempty,noterminate,nodone,withbranch,noreleased', $projectID, 'project');
}
else
{
$openedBuilds = $this->build->getBuildPairs($productID, $bug->branch, 'noempty,noterminate,nodone,withbranch,noreleased');
}
/* Set the openedBuilds list. */
$oldOpenedBuilds = array();
$bugOpenedBuilds = explode(',', $bug->openedBuild);
foreach($bugOpenedBuilds as $buildID)
{
if(isset($allBuilds[$buildID])) $oldOpenedBuilds[$buildID] = $allBuilds[$buildID];
}
$openedBuilds = $openedBuilds + $oldOpenedBuilds;
/* Set the resolvedBuilds list. */
$oldResolvedBuild = array();
if(($bug->resolvedBuild) and isset($allBuilds[$bug->resolvedBuild])) $oldResolvedBuild[$bug->resolvedBuild] = $allBuilds[$bug->resolvedBuild];
$projectID = $this->lang->navGroup->bug == 'project' ? $this->session->project : 0;
if($this->app->tab == 'execution' or $this->app->tab == 'project')
{
$objectID = $this->app->tab == 'project' ? $bug->project : $bug->execution;
}
/* Display status of branch. */
$branches = $this->loadModel('branch')->getList($productID, isset($objectID) ? $objectID : 0, 'all');
$branchOption = array();
$branchTagOption = array();
foreach($branches as $branchInfo)
{
$branchOption[$branchInfo->id] = $branchInfo->name;
$branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
}
if(!isset($branchTagOption[$bug->branch]))
{
$bugBranch = $this->branch->getById($bug->branch, $bug->product, '');
$branchTagOption[$bug->branch] = $bug->branch == BRANCH_MAIN ? $bugBranch : ($bugBranch->name . ($bugBranch->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : ''));
}
$moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'bug', $startModuleID = 0, $bug->branch);
if(!isset($moduleOptionMenu[$bug->module])) $moduleOptionMenu += $this->tree->getModulesName($bug->module);
$cases = $this->loadmodel('testcase')->getPairsByProduct($bug->product, array(0, $bug->branch));
/* Get assigned to member. */
if($bug->execution)
{
$assignedToList = $this->user->getTeamMemberPairs($bug->execution, 'execution');
}
elseif($bug->project)
{
$assignedToList = $this->loadModel('project')->getTeamMemberPairs($bug->project);
}
else
{
$assignedToList = $this->bug->getProductMemberPairs($bug->product, $bug->branch);
$assignedToList = array_filter($assignedToList);
if(empty($assignedToList)) $assignedToList = $this->user->getPairs('devfirst|noclosed');
}
if($bug->assignedTo and !isset($assignedToList[$bug->assignedTo]) and $bug->assignedTo != 'closed')
{
/* Fix bug #28378. */
$assignedTo = $this->user->getById($bug->assignedTo);
$assignedToList[$bug->assignedTo] = $assignedTo->realname;
}
if($bug->status == 'closed') $assignedToList['closed'] = 'Closed';
$branch = $product->type == 'branch' ? ($bug->branch > 0 ? $bug->branch . ',0' : '0') : '';
$productBugs = $this->bug->getProductBugPairs($productID, $branch);
unset($productBugs[$bugID]);
$executions = array(0 => '') + $this->product->getExecutionPairsByProduct($bug->product, $bug->branch, 'id_desc', $bug->project);
if(!empty($bug->execution) and empty($executions[$bug->execution])) $executions[$execution->id] = $execution->name . "({$this->lang->bug->deleted})";
$projects = array(0 => '') + $this->product->getProjectPairsByProduct($productID, $bug->branch);
if(!empty($bug->project) and empty($projects[$bug->project]))
{
$project = $this->loadModel('project')->getByID($bug->project);
$projects[$project->id] = $project->name . "({$this->lang->bug->deleted})";
}
if($product->shadow) $this->view->project = $this->loadModel('project')->getByShadowProduct($bug->product);
$this->view->bug = $bug;
$this->view->product = $product;
$this->view->execution = $execution;
$this->view->productBugs = $productBugs;
$this->view->productName = $this->products[$productID];
$this->view->plans = $this->loadModel('productplan')->getPairs($productID, $bug->branch, '', true);
$this->view->projects = $projects;
$this->view->projectExecutionPairs = $this->loadModel('project')->getProjectExecutionPairs();
$this->view->moduleOptionMenu = $moduleOptionMenu;
$this->view->currentModuleID = $currentModuleID;
$this->view->executions = $executions;
$this->view->stories = $bug->execution ? $this->story->getExecutionStoryPairs($bug->execution) : $this->story->getProductStoryPairs($bug->product, $bug->branch, 0, 'all', 'id_desc', 0, 'full', 'story', false);
$this->view->branchOption = $branchOption;
$this->view->branchTagOption = $branchTagOption;
$this->view->tasks = $this->task->getExecutionTaskPairs($bug->execution);
$this->view->testtasks = $this->loadModel('testtask')->getPairs($bug->product, $bug->execution, $bug->testtask);
$this->view->users = $this->user->getPairs('', "$bug->assignedTo,$bug->resolvedBy,$bug->closedBy,$bug->openedBy");
$this->view->assignedToList = $assignedToList;
$this->view->cases = array('' => '') + $cases;
$this->view->openedBuilds = $openedBuilds;
$this->view->resolvedBuilds = array('' => '') + $openedBuilds + $oldResolvedBuild;
$this->view->actions = $this->action->getList('bug', $bugID);
$this->display();
$this->bugZen->buildEditForm($bug);
}
/**
+29 -120
View File
@@ -174,7 +174,7 @@ class bugModel extends model
foreach($extendFields as $extendField)
{
$bug->{$extendField->field} = $this->post->{$extendField->field}[$i];
if(is_array($bug->{$extendField->field})) $bug->{$extendField->field} = join(',', $bug->{$extendField->field});
if(is_array($bug->{$extendField->field})) $bug->{$extendField->field} = implode(',', $bug->{$extendField->field});
$bug->{$extendField->field} = htmlSpecialString($bug->{$extendField->field});
}
@@ -397,7 +397,7 @@ class bugModel extends model
* @access public
* @return object|false
*/
public function getByID(int $bugID, bool $setImgSize = false): object|false
public function getByID(string $bugID, bool $setImgSize = false): object|false
{
$bug = $this->bugTao->fetchBugInfo($bugID);
if(!$bug) return false;
@@ -532,121 +532,33 @@ class bugModel extends model
}
/**
* 更新bug信息。
* Update a bug.
*
* @param int $bugID
* @param object $bug
* @param object $oldBug
* @access public
* @return void
* @return array|false
*/
public function update($bugID)
public function update(object $bug, object $oldBug): array|false
{
$oldBug = $this->getById($bugID);
if(!empty($_POST['lastEditedDate']) and $oldBug->lastEditedDate != $this->post->lastEditedDate)
{
dao::$errors[] = $this->lang->error->editedByOther;
return false;
}
$now = helper::now();
$bug = fixer::input('post')
->add('id', $bugID)
->cleanInt('product,module,severity,project,execution,story,task,branch')
->stripTags($this->config->bug->editor->edit['id'], $this->config->allowedTags)
->setDefault('module,execution,story,task,duplicateBug,branch', 0)
->setDefault('product', $oldBug->product)
->setDefault('openedBuild', '')
->setDefault('os', '')
->setDefault('browser', '')
->setDefault('plan', 0)
->setDefault('deadline', '0000-00-00')
->setDefault('resolvedDate', '')
->setDefault('lastEditedBy', $this->app->user->account)
->setDefault('mailto', '')
->setDefault('deleteFiles', array())
->add('lastEditedDate', $now)
->setIF(strpos($this->config->bug->edit->requiredFields, 'deadline') !== false, 'deadline', $this->post->deadline)
->join('openedBuild', ',')
->join('mailto', ',')
->join('linkBug', ',')
->join('os', ',')
->join('browser', ',')
->setIF($this->post->assignedTo != $oldBug->assignedTo, 'assignedDate', $now)
->setIF($this->post->resolvedBy != '' and $this->post->resolvedDate == '', 'resolvedDate', $now)
->setIF($this->post->resolution != '' and $this->post->resolvedDate == '', 'resolvedDate', $now)
->setIF($this->post->resolution != '' and $this->post->resolvedBy == '', 'resolvedBy', $this->app->user->account)
->setIF($this->post->closedBy != '' and $this->post->closedDate == '', 'closedDate', $now)
->setIF($this->post->closedDate != '' and $this->post->closedBy == '', 'closedBy', $this->app->user->account)
->setIF($this->post->closedBy != '' or $this->post->closedDate != '', 'assignedTo', 'closed')
->setIF($this->post->closedBy != '' or $this->post->closedDate != '', 'assignedDate', $now)
->setIF($this->post->resolution != '' or $this->post->resolvedDate != '', 'status', 'resolved')
->setIF($this->post->closedBy != '' or $this->post->closedDate != '', 'status', 'closed')
->setIF(($this->post->resolution != '' or $this->post->resolvedDate != '') and $this->post->assignedTo == '', 'assignedTo', $oldBug->openedBy)
->setIF(($this->post->resolution != '' or $this->post->resolvedDate != '') and $this->post->assignedTo == '', 'assignedDate', $now)
->setIF($this->post->assignedTo == '' and $oldBug->status == 'closed', 'assignedTo', 'closed')
->setIF($this->post->resolution == '' and $this->post->resolvedDate =='', 'status', 'active')
->setIF($this->post->resolution != '', 'confirmed', 1)
->setIF($this->post->resolution != '' and $this->post->resolution != 'duplicate', 'duplicateBug', 0)
->setIF($this->post->story != false and $this->post->story != $oldBug->story, 'storyVersion', $this->loadModel('story')->getVersion($this->post->story))
->setIF(!$this->post->linkBug, 'linkBug', '')
->setIF($this->post->case === '', 'case', 0)
->remove('comment,files,labels,uid,contactListMenu')
->get();
$bug = $this->loadModel('file')->processImgURL($bug, $this->config->bug->editor->edit['id'], $this->post->uid);
$this->dao->update(TABLE_BUG)->data($bug, 'deleteFiles')
->autoCheck()
->batchCheck($this->config->bug->edit->requiredFields, 'notempty')
->checkIF($bug->resolvedBy, 'resolution', 'notempty')
->checkIF($bug->closedBy, 'resolution', 'notempty')
->checkIF($bug->notifyEmail, 'notifyEmail', 'email')
->checkIF($bug->notifyEmail,'notifyEmail', 'email')
->checkIF($bug->resolution == 'duplicate', 'duplicateBug', 'notempty')
->checkIF($bug->resolution == 'fixed', 'resolvedBuild','notempty')
->checkFlow()
->where('id')->eq((int)$bugID)
->where('id')->eq($bug->id)
->exec();
if(!dao::isError())
{
/* Link bug to build and release. */
if($bug->resolution == 'fixed' and !empty($bug->resolvedBuild) and $oldBug->resolvedBuild != $bug->resolvedBuild)
{
if(!empty($oldBug->resolvedBuild)) $this->loadModel('build')->unlinkBug($oldBug->resolvedBuild, (int)$bugID);
$this->linkBugToBuild($bugID, $bug->resolvedBuild);
}
if(dao::isError()) return false;
if($bug->plan != $oldBug->plan)
{
$this->loadModel('action');
if(!empty($oldBug->plan)) $this->action->create('productplan', $oldBug->plan, 'unlinkbug', '', $bugID);
if(!empty($bug->plan)) $this->action->create('productplan', $bug->plan, 'linkbug', '', $bugID);
}
if(!$this->bugTao->afterUpdate($bug, $oldBug)) return false;
$linkBugs = explode(',', $bug->linkBug);
$oldLinkBugs = explode(',', $oldBug->linkBug);
$addBugs = array_diff($linkBugs, $oldLinkBugs);
$removeBugs = array_diff($oldLinkBugs, $linkBugs);
$changeBugs = array_merge($addBugs, $removeBugs);
$changeBugs = $this->dao->select('id,linkbug')->from(TABLE_BUG)->where('id')->in(array_filter($changeBugs))->fetchPairs();
foreach($changeBugs as $changeBugID => $changeBug)
{
if(in_array($changeBugID, $addBugs) and empty($changeBug)) $this->dao->update(TABLE_BUG)->set('linkBug')->eq($bugID)->where('id')->eq((int)$changeBugID)->exec();
if(in_array($changeBugID, $addBugs) and !empty($changeBug)) $this->dao->update(TABLE_BUG)->set('linkBug')->eq("$changeBug,$bugID")->where('id')->eq((int)$changeBugID)->exec();
if(in_array($changeBugID, $removeBugs))
{
$linkBugs = explode(',', $changeBug);
unset($linkBugs[array_search($bugID, $linkBugs)]);
$this->dao->update(TABLE_BUG)->set('linkBug')->eq(implode(',', $linkBugs))->where('id')->eq((int)$changeBugID)->exec();
}
}
if(!empty($bug->resolvedBy)) $this->loadModel('score')->create('bug', 'resolve', $bugID);
if($bug->execution and $bug->status != $oldBug->status) $this->loadModel('kanban')->updateLane($bug->execution, 'bug');
if(($this->config->edition == 'biz' || $this->config->edition == 'max') && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status);
$this->file->processFile4Object('bug', $oldBug, $bug);
return common::createChanges($oldBug, $bug);
}
return common::createChanges($oldBug, $bug);
}
/**
@@ -739,7 +651,7 @@ class bugModel extends model
foreach($extendFields as $extendField)
{
$bug->{$extendField->field} = $this->post->{$extendField->field}[$bugID];
if(is_array($bug->{$extendField->field})) $bug->{$extendField->field} = join(',', $bug->{$extendField->field});
if(is_array($bug->{$extendField->field})) $bug->{$extendField->field} = implode(',', $bug->{$extendField->field});
$bug->{$extendField->field} = htmlSpecialString($bug->{$extendField->field});
}
@@ -846,7 +758,7 @@ class bugModel extends model
{
$postFieldData = $this->post->{$extendField->field};
if(is_array($postFieldData[$bugID])) $postFieldData[$bugID] = join(',', $postFieldData[$bugID]);
if(is_array($postFieldData[$bugID])) $postFieldData[$bugID] = implode(',', $postFieldData[$bugID]);
$activateBugs[$bugID][$extendField->field] = htmlSpecialString($postFieldData[$bugID]);
}
@@ -1524,7 +1436,7 @@ class bugModel extends model
$builds = $this->loadModel('build')->getBuildPairs(array_unique($productIdList), 'all', $params = '');
/* Process the openedBuild and resolvedBuild fields. */
foreach($bugs as $key => $bug)
foreach($bugs as $bug)
{
$openBuildIdList = explode(',', $bug->openedBuild);
$openedBuild = '';
@@ -1618,7 +1530,7 @@ class bugModel extends model
}
else
{
if($this->session->$queryName == false) $this->session->set($queryName, ' 1 = 1');
if($this->session->$queryName === false) $this->session->set($queryName, ' 1 = 1');
}
$query = $this->session->$queryName;
$query = preg_replace('/`(\w+)`/', 't1.`$1`', $query);
@@ -1711,7 +1623,7 @@ class bugModel extends model
if($type == 'bysearch')
{
$queryID = (int)$param;
if($this->session->projectBugQuery == false) $this->session->set('projectBugQuery', ' 1 = 1');
if($this->session->projectBugQuery === false) $this->session->set('projectBugQuery', ' 1 = 1');
if($queryID)
{
$query = $this->loadModel('search')->getQuery($queryID);
@@ -1782,7 +1694,7 @@ class bugModel extends model
if($type == 'bysearch')
{
$queryID = (int)$param;
if($this->session->executionBugQuery == false) $this->session->set('executionBugQuery', ' 1 = 1');
if($this->session->executionBugQuery === false) $this->session->set('executionBugQuery', ' 1 = 1');
if($queryID)
{
$query = $this->loadModel('search')->getQuery($queryID);
@@ -1818,7 +1730,7 @@ class bugModel extends model
{
if($build) $conditions[] = "FIND_IN_SET('$build', t1.openedBuild)";
}
$condition = join(' OR ', $conditions);
$condition = implode(' OR ', $conditions);
$condition = "($condition)";
}
$bugs = $this->dao->select("t1.*, IF(t1.`pri` = 0, {$this->config->maxPriValue}, t1.`pri`) as priOrder, IF(t1.`severity` = 0, {$this->config->maxPriValue}, t1.`severity`) as severityOrder")->from(TABLE_BUG)->alias('t1')
@@ -1876,7 +1788,7 @@ class bugModel extends model
->andWhere('t1.date')->lt($minBegin)
->fetchPairs('id', 'id');
$bugs = $this->dao->select('*')->from(TABLE_BUG)->where('deleted')->eq(0)
return $this->dao->select('*')->from(TABLE_BUG)->where('deleted')->eq(0)
->andWhere('product')->eq($productID)
->andWhere('toStory')->eq(0)
->andWhere('openedDate')->ge($minBegin)
@@ -1887,8 +1799,6 @@ class bugModel extends model
->beginIF($branch !== '')->andWhere('branch')->in("0,$branch")->fi()
->page($pager)
->fetchAll();
return $bugs;
}
/**
@@ -1976,7 +1886,7 @@ class bugModel extends model
$condition .= " OR (`execution` = '{$execution->id}' AND openedDate < '{$execution->begin}')";
}
$bugs = $this->dao->select('*')->from(TABLE_BUG)
return $this->dao->select('*')->from(TABLE_BUG)
->where('resolvedDate')->ge($minBegin)
->andWhere('resolution')->ne('postponed')
->andWhere('product')->eq($productID)
@@ -1987,7 +1897,6 @@ class bugModel extends model
->orderBy('openedDate ASC')
->page($pager)
->fetchAll('id');
return $bugs;
}
/**
@@ -2394,7 +2303,7 @@ class bugModel extends model
{
$datas = $this->dao->select('pri AS name, COUNT(*) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
if(!$datas) return array();
foreach($datas as $status => $data) $data->name = $this->lang->bug->report->bugsPerPri->graph->xAxisName . ':' . zget($this->lang->bug->priList, $data->name);
foreach($datas as $data) $data->name = $this->lang->bug->report->bugsPerPri->graph->xAxisName . ':' . zget($this->lang->bug->priList, $data->name);
return $datas;
}
@@ -2528,7 +2437,7 @@ class bugModel extends model
}
else
{
if($this->session->bugQuery == false) $this->session->set('bugQuery', ' 1 = 1');
if($this->session->bugQuery === false) $this->session->set('bugQuery', ' 1 = 1');
}
$bugQuery = $this->getBugQuery($this->session->bugQuery);
@@ -2557,7 +2466,7 @@ class bugModel extends model
if($branch !== 'all' and strpos($bugQuery, '`branch` =') === false) $bugQuery .= " AND `branch` in('0','$branch')";
if(strpos($bugQuery, $allBranch) !== false) $bugQuery = str_replace($allBranch, '1', $bugQuery);
$bugs = $this->dao->select("*, IF(`pri` = 0, {$this->config->maxPriValue}, `pri`) as priOrder, IF(`severity` = 0, {$this->config->maxPriValue}, `severity`) as severityOrder")->from(TABLE_BUG)->where($bugQuery)
return $this->dao->select("*, IF(`pri` = 0, {$this->config->maxPriValue}, `pri`) as priOrder, IF(`severity` = 0, {$this->config->maxPriValue}, `severity`) as severityOrder")->from(TABLE_BUG)->where($bugQuery)
->beginIF(!$this->app->user->admin)->andWhere('execution')->in('0,' . $this->app->user->view->sprints)->fi()
->beginIF($excludeBugs)->andWhere('id')->notIN($excludeBugs)->fi()
@@ -2570,8 +2479,8 @@ class bugModel extends model
->andWhere('deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('project')->in('0,' . $this->app->user->view->projects)->fi()
->orderBy($orderBy)->page($pager)->fetchAll();
return $bugs;
->orderBy($orderBy)->page($pager)
->fetchAll();
}
/**
@@ -2749,13 +2658,13 @@ class bugModel extends model
public function linkBugToBuild($bugs, $resolvedBuild)
{
if(empty($resolvedBuild) or $resolvedBuild == 'trunk') return true;
if(is_array($bugs)) $bugs = join(',', $bugs);
if(is_array($bugs)) $bugs = implode(',', $bugs);
$build = $this->dao->select('id,product,bugs')->from(TABLE_BUILD)->where('id')->eq($resolvedBuild)->fetch();
$buildBugs = $build->bugs . ',' . $bugs;
$buildBugs = explode(',', trim($buildBugs, ','));
$buildBugs = array_unique($buildBugs);
$this->dao->update(TABLE_BUILD)->set('bugs')->eq(join(',', $buildBugs))->where('id')->eq($resolvedBuild)->exec();
$this->dao->update(TABLE_BUILD)->set('bugs')->eq(implode(',', $buildBugs))->where('id')->eq($resolvedBuild)->exec();
$release = $this->dao->select('id,bugs')->from(TABLE_RELEASE)->where('product')->eq($build->product)->andWhere("(FIND_IN_SET('$resolvedBuild', build) or shadow = $resolvedBuild)")->andWhere('deleted')->eq('0')->fetch();
if($release)
@@ -2763,7 +2672,7 @@ class bugModel extends model
$releaseBugs = $release->bugs . ',' . $bugs;
$releaseBugs = explode(',', trim($releaseBugs, ','));
$releaseBugs = array_unique($releaseBugs);
$this->dao->update(TABLE_RELEASE)->set('bugs')->eq(join(',', $releaseBugs))->where('id')->eq($release->id)->exec();
$this->dao->update(TABLE_RELEASE)->set('bugs')->eq(implode(',', $releaseBugs))->where('id')->eq($release->id)->exec();
}
return true;
+93 -2
View File
@@ -10,7 +10,7 @@ class bugTao extends bugModel
* @access protected
* @return object|false
*/
protected function fetchBugInfo(int $bugID): object|false
protected function fetchBugInfo(string $bugID): object|false
{
return $this->dao->select('t1.*, t2.name AS executionName, t3.title AS storyTitle, t3.status AS storyStatus, t3.version AS latestStoryVersion, t4.name AS taskName, t5.title AS planName')
->from(TABLE_BUG)->alias('t1')
@@ -136,7 +136,7 @@ class bugTao extends bugModel
* @access protected
* @return array
*/
protected function getCasesFromBug(int $bugID): array
protected function getCasesFromBug(string $bugID): array
{
return $this->dao->select('id, title')->from(TABLE_CASE)->where('`fromBug`')->eq($bugID)->fetchPairs();
}
@@ -169,6 +169,97 @@ class bugTao extends bugModel
return $this->dao->findById($objectID)->from($table)->fields($field)->fetch($field);
}
/**
* 更新完bug后的相关处理。
* Relevant processing after updating bug.
*
* @param object $bug
* @param object $oldBug
* @access protected
* @return void
*/
protected function afterUpdate(object $bug, object $oldBug)
{
/* 解除旧的版本关联关系,关联新的版本。*/
/* Link bug to build and release. */
if($bug->resolution == 'fixed' && !empty($bug->resolvedBuild) && $bug->resolvedBuild != $oldBug->resolvedBuild)
{
if(!empty($oldBug->resolvedBuild)) $this->loadModel('build')->unlinkBug($oldBug->resolvedBuild, $bug->id);
$this->linkBugToBuild($bug->id, $bug->resolvedBuild);
}
/* 解除旧的计划关联关系,关联新的计划。*/
/* Link new plan, unlink old plan. */
if($bug->plan != $oldBug->plan)
{
$this->loadModel('action');
if(!empty($oldBug->plan)) $this->action->create('productplan', $oldBug->plan, 'unlinkbug', '', $bug->id);
if(!empty($bug->plan)) $this->action->create('productplan', $bug->plan, 'linkbug', '', $bug->id);
}
$this->updateLinkBug($bug->id, $bug->linkBug, $oldBug->linkBug);
/* 给bug解决者积分奖励。*/
/* Add score to resolvedby. */
if(!empty($bug->resolvedBy)) $this->loadModel('score')->create('bug', 'resolve', $bug->id);
/* 更新bug所属看板的泳道。*/
/* Update the lane of the bug kanban. */
if($bug->execution and $bug->status != $oldBug->status) $this->loadModel('kanban')->updateLane($bug->execution, 'bug');
/* 更新反馈的状态。*/
/* Update the status of feedback. */
if(($this->config->edition != 'open') && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status);
/* 更新bug的附件。*/
/* Update the files of bug. */
$this->loadModel('file')->processFile4Object('bug', $oldBug, $bug);
return !dao::isError();
}
/**
* 更新相关bug。
* Update the linked bug.
*
* @param int $bugID
* @param string $linkBug
* @param string $oldLinkBug
* @access private
* @return bool
*/
private function updateLinkBug(int $bugID, string $linkBug, string $oldLinkBug): bool
{
return false;
$linkBugs = explode(',', $linkBug);
$oldLinkBugs = explode(',', $oldLinkBug);
$addedLinkBugs = array_diff($linkBugs, $oldLinkBugs);
$removedLinkBugs = array_diff($oldLinkBugs, $linkBugs);
$changedLinkBugs = array_merge($addedLinkBugs, $removedLinkBugs);
$changedLinkBugs = $this->dao->select('id, linkbug')->from(TABLE_BUG)->where('id')->in(array_filter($changedLinkBugs))->fetchPairs();
foreach($changedLinkBugs as $changedBugID => $linkBugs)
{
if(in_array($changeBugID, $addedLinkBugs))
{
$currentLinkBug = $bugID;
if(!empty($linkBugs)) $currentLinkBug = trim($linkBugs, ',') . ',' . $bugID;
}
else
{
$linkBugs = explode(',', $linkBugs);
unset($linkBugs[array_search($bugID, $linkBugs)]);
$currentLinkBug = implode(',', $linkBugs);
}
$this->dao->update(TABLE_BUG)->set('linkBug')->eq($linkBug)->where('id')->eq($changeBugID)->exec();
}
return !dao::isError();
}
/**
* Call checkDelayBug in foreach to check if the bug is delay.
* 循环调用checkDelayBug,检查bug是否延期
+3 -2
View File
@@ -795,10 +795,11 @@ class bugTest
$_POST[$field] = $value;
}
}
$_POST['closedDate'] = '';
$_POST['deleteFiles'] = array();
$object->files = array();
$change = $this->objectModel->update($bugID);
$change = $this->objectModel->update((object)$_POST, $object);
if($change == array()) $change = '没有数据更新';
unset($_POST);
+4 -4
View File
@@ -45,7 +45,7 @@ $tester->loadModel('bug');
initData();
r($tester->bug->getByID(2)) && p('pri,type') && e('1,codeerror'); //获取ID等于2的bug
r($tester->bug->getByID(3)) && p('title,status') && e('bug3,active'); //获取ID等于3的bug
r($tester->bug->getByID(4)) && p('severity,openedBuild') && e('3,trunk'); //获取ID等于4的bug
r($tester->bug->getByID(1)) && p('title') && e('0'); //获取不存在的bug
r($tester->bug->getByID(2)) && p('pri,type') && e('1,codeerror'); //获取ID等于2的bug
r($tester->bug->getByID(3)) && p('title,status') && e('bug3,active'); //获取ID等于3的bug
r($tester->bug->getByID(4)) && p('severity,openedBuild') && e('3,trunk'); //获取ID等于4的bug
r($tester->bug->getByID(1)) && p('title') && e('0'); //获取不存在的bug
+48 -19
View File
@@ -1,33 +1,62 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php"; su('admin');
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/bug.class.php';
su('admin');
function initData()
{
$data = zdTable('bug');
$data->id->range('1-5');
$data->product->range('1-5');
$data->branch->range('0-1');
$data->project->range('0-5');
$data->execution->range('0-5');
$data->title->prefix("BUG")->range('1-5');
$data->openedBuild->range('1-5');
$data->type->range("[codeerror]");
$data->status->range("[active]");
$data->pri->range("[3]");
$data->severity->range("[3]");
$data->gen(4);
}
/**
title=bugModel->update();
timeout=0
cid=1
pid=1
测试更新bug名称 >> title,BUG1,john
测试更新bug类型 >> type,codeerror,config
测试更新bug名称和类型 >> title,john,jack;type,config,install
测试不更改bug名称 >> 没有数据更新
测试不更改bug类型 >> 没有数据更新
- 测试更新bug名称
- 第0条的field属性 @title
- 第0条的old属性 @BUG1
- 第0条的new属性 @john
- 测试更新bug类型
- 第0条的field属性 @type
- 第0条的old属性 @codeerror
- 第0条的new属性 @config
- 测试不更改bug名称 @没有数据更新
- 测试不更改bug类型 @没有数据更新
*/
$projectIdList = array('1', '2');
initData();
$t_uptitle = array('title' => 'john');
$t_uptype = array('type' => 'config');
$t_typetitle = array('title' => 'jack', 'type' => 'install');
$t_untitle = array('title' => 'jack');
$t_untype = array('type' => 'install');
$bugIdList = array('1', '2');
$bug=new bugTest();
r($bug->updateObject($projectIdList[0], $t_uptitle)) && p('0:field,old,new') && e('title,BUG1,john'); // 测试更新bug名称
r($bug->updateObject($projectIdList[0], $t_uptype)) && p('0:field,old,new') && e('type,codeerror,config'); // 测试更新bug类型
r($bug->updateObject($projectIdList[0], $t_typetitle)) && p('0:field,old,new;1:field,old,new') && e('title,john,jack;type,config,install'); // 测试更新bug名称和类型
r($bug->updateObject($projectIdList[0], $t_untitle)) && p() && e('没有数据更新'); // 测试不更改bug名称
r($bug->updateObject($projectIdList[0], $t_untype)) && p() && e('没有数据更新'); // 测试不更改bug类型
$t_uptitle = array('title' => 'john');
$t_uptype = array('type' => 'config');
$t_untitle = array('title' => 'john');
$t_untype = array('type' => 'config');
$bug = new bugTest();
r($bug->updateObject($bugIdList[0], $t_uptitle)) && p('0:field,old,new') && e('title,BUG1,john'); // 测试更新bug名称
r($bug->updateObject($bugIdList[0], $t_uptype)) && p('0:field,old,new') && e('type,codeerror,config'); // 测试更新bug类型
r($bug->updateObject($bugIdList[0], $t_untitle)) && p() && e('没有数据更新'); // 测试不更改bug名称
r($bug->updateObject($bugIdList[0], $t_untype)) && p() && e('没有数据更新'); // 测试不更改bug类型
+2 -2
View File
@@ -37,7 +37,7 @@ if($this->app->tab == 'project') js::set('objectID', $bug->project);
?>
<div class='main-content' id='mainContent'>
<form method='post' target='hiddenwin' enctype='multipart/form-data' id='dataform'>
<form method='post' target='hiddenwin' enctype='multipart/form-data' id='dataform ajaxForm' class='form-ajax'>
<div class='main-header'>
<h2>
<span class='label label-id'><?php echo $bug->id;?></span>
@@ -112,7 +112,7 @@ if($this->app->tab == 'project') js::set('objectID', $bug->project);
<td>
<div class='input-group' id='moduleIdBox'>
<?php
echo html::select('module', $moduleOptionMenu, $currentModuleID, "onchange='loadModuleRelated()' class='form-control chosen'");
echo html::select('module', $moduleOptionMenu, $bug->module, "onchange='loadModuleRelated()' class='form-control chosen'");
if(count($moduleOptionMenu) == 1)
{
echo "<span class='input-group-addon'>";
+316 -1
View File
@@ -29,7 +29,6 @@ class bugZen extends bug
return $bug;
}
/**
* 创建bug。
* Create a bug.
@@ -100,6 +99,159 @@ class bugZen extends bug
if($from && is_callable(array($this, $this->config->bug->fromObjects[$from]['callback']))) call_user_func(array($this, $this->config->bug->fromObjects[$from]['callback']), $bugID);
}
/**
* 处理更新请求数据。
* Processing request data.
*
* @param string $bugID
* @param form $formData
* @access protected
* @return object|false
*/
protected function prepareEditExtras(form $formData, object $oldBug): object|false
{
if($oldBug->lastEditedDate != $formData->data->lastEditedDate)
{
dao::$errors[] = $this->lang->error->editedByOther;
return false;
}
$now = helper::now();
$bug = $formData->add('id', $oldBug->id)
->setDefault('product', $oldBug->product)
->setDefault('deleteFiles', array())
->setDefault('lastEditedBy', $this->app->user->account)
->add('lastEditedDate', $now)
->join('openedBuild,mailto,linkBug,os,browser', ',')
->setIF($formData->data->assignedTo != $oldBug->assignedTo, 'assignedDate', $now)
->setIF($formData->data->resolvedBy != '' && $formData->data->resolvedDate == '', 'resolvedDate', $now)
->setIF($formData->data->resolution != '' && $formData->data->resolvedDate == '', 'resolvedDate', $now)
->setIF($formData->data->resolution != '' && $formData->data->resolvedBy == '', 'resolvedBy', $this->app->user->account)
->setIF($formData->data->closedDate != '' && $formData->data->closedBy == '', 'closedBy', $this->app->user->account)
->setIF($formData->data->closedBy != '' && $formData->data->closedDate == '', 'closedDate', $now)
->setIF($formData->data->closedBy != '' || $formData->data->closedDate != '', 'assignedTo', 'closed')
->setIF($formData->data->closedBy != '' || $formData->data->closedDate != '', 'assignedDate', $now)
->setIF($formData->data->resolution != '' || $formData->data->resolvedDate != '', 'status', 'resolved')
->setIF($formData->data->closedBy != '' || $formData->data->closedDate != '', 'status', 'closed')
->setIF(($formData->data->resolution != '' || $formData->data->resolvedDate != '') && $formData->data->assignedTo == '', 'assignedTo', $oldBug->openedBy)
->setIF(($formData->data->resolution != '' || $formData->data->resolvedDate != '') && $formData->data->assignedTo == '', 'assignedDate', $now)
->setIF($formData->data->resolution == '' && $formData->data->resolvedDate == '', 'status', 'active')
->setIF($formData->data->resolution != '' && $formData->data->resolution != 'duplicate', 'duplicateBug', 0)
->setIF($formData->data->assignedTo == '' && $oldBug->status == 'closed', 'assignedTo', 'closed')
->setIF($formData->data->resolution != '', 'confirmed', 1)
->setIF($formData->data->story && $formData->data->story != $oldBug->story, 'storyVersion', $this->loadModel('story')->getVersion($formData->data->story))
->stripTags($this->config->bug->editor->edit['id'], $this->config->allowedTags)
->get();
$bug = $this->loadModel('file')->processImgURL($bug, $this->config->bug->editor->create['id'], $bug->uid);
return $bug;
}
/**
* 返回错误信息。
* return error.
*
* @access protected
* @return array|viod
*/
protected function errorEdit()
{
if(defined('RUN_MODE') && RUN_MODE == 'api') return array('status' => 'error', 'message' => dao::getError());
return array('result' => 'fail', 'message' => dao::getError());
}
/**
* 更新成功后的相关处理。
* Relevant processing after updating bug.
*
* @param int $bugID
* @param string $comment
* @param array $changes
* @access protected
* @return void
*/
protected function processAfterEdit(int $bugID, string $comment, array $changes)
{
if($this->post->comment || !empty($changes))
{
$action = !empty($changes) ? 'Edited' : 'Commented';
$actionID = $this->action->create('bug', $bugID, $action, $comment);
$this->action->logHistory($actionID, $changes);
}
}
/**
* 返回不同的结果。
* Response after updating bug.
*
* @param string $bugID
* @param array $changes
* @param string $kanbanGroup
* @access protected
* @return array
*/
protected function responseAfterEdit(string $bugID, array $changes, string $kanbanGroup): array
{
if(defined('RUN_MODE') && RUN_MODE == 'api') return array('status' => 'success', 'data' => $bugID);
/* 如果bug转任务,如果bug的状态发生变化,提示是否更新任务状态。*/
/* This bug has been converted to a task, update the status of the relatedtask or not. */
$bug = $this->bug->getByID($bugID);
if($bug->toTask and !empty($changes))
{
foreach($changes as $change)
{
if($change['field'] != 'status') continue;
return array('result' => 'success', 'confirm' => array('note' => $this->lang->bug->remindTask, 'confirmURL' => $this->createLink('task', 'view', "taskID=$bug->toTask"), 'cancelURL' => $this->server->HTTP_REFERER));
}
}
/* 弹窗里编辑bug的返回。*/
/* Response when edit in modal. */
if(isonlybody())
{
/* 在执行应用下,编辑看板中的bug数据时,更新看板数据。*/
/* Update kanban data after edited bug in kanban. */
if($this->app->tab == 'execution')
{
$this->loadModel('kanban');
$execution = $this->loadModel('execution')->getByID($bug->execution);
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
/* 1.看板类型的执行。*/
/* 1.The kanban exectuion. */
if(isset($execution->type) && $execution->type == 'kanban')
{
$rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : '';
$kanbanData = $this->kanban->getRDKanban($bug->execution, $execLaneType, 'id_desc', 0, $kanbanGroup, $rdSearchValue);
$kanbanData = json_encode($kanbanData);
return array('result' => 'success', 'closeModal' => true, 'callback' => "updateKanban($kanbanData)");
}
/* 2.执行中的看板。*/
/* 2.The kanban of execution. */
else
{
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
$taskSearchValue = $this->session->taskSearchValue ? $this->session->taskSearchValue : '';
$kanbanData = $this->kanban->getExecutionKanban($bug->execution, $execLaneType, $execGroupBy, $taskSearchValue);
$kanbanType = $execLaneType == 'all' ? 'bug' : key($kanbanData);
$kanbanData = json_encode($kanbanData[$kanbanType]);
return array('result' => 'success', 'closeModal' => true, 'callback' => "updateKanban(\"bug\", $kanbanData)");
}
}
return array('result' => 'success', 'closeModal' => true);
}
return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('bug', 'view', "bugID=$bugID"));
}
/**
* 为create方法添加动态。
* Add action for create function.
@@ -625,6 +777,169 @@ class bugZen extends bug
$this->app->loadLang('release');
}
/**
* 设置编辑页面的导航。
* Set edit menu.
*
* @param object $bug
* @access protected
* @return void
*/
protected function setEditMenu(object $bug)
{
if($this->app->tab == 'project') $this->project->setMenu($bug->project);
if($this->app->tab == 'execution') $this->execution->setMenu($bug->execution);
if($this->app->tab == 'qa') $this->qa->setMenu($this->products, $bug->product, $bug->branch);
/* 是否有用?*/
if($this->app->tab == 'devops')
{
session_write_close();
$repoPairs = $this->loadModel('repo')->getRepoPairs('project', $bug->project);
$this->repo->setMenu($repoPairs);
$this->lang->navGroup->bug = 'devops';
}
}
/**
* 获取页面所需的变量, 并输出到前台。
* Get the data required by the view page and output.
*
* @param object $bug
* @access protected
* @return void
*/
protected function buildEditForm(object $bug)
{
/* 删掉当前bug类型不属于的并且已经弃用的类型。*/
/* Unset discarded types. */
foreach($this->config->bug->discardedTypes as $type)
{
if($bug->type != $type) unset($this->lang->bug->typeList[$type]);
}
$this->loadModel('project');
$this->loadModel('branch');
$this->loadModel('execution');
$this->loadModel('build');
$product = $this->product->getByID($bug->product);
$execution = $this->execution->getByID($bug->execution);
/* 获取影响版本列表和解决版本列表。*/
/* Get the openedBuilds and resolvedBuilds. */
$objectID = $bug->execution ? $bug->execution : $bug->project;
$objectType = $bug->project ? 'project' : '';
$objectType = $bug->execution ? 'execution' : $objectType;
$allBuilds = $this->loadModel('build')->getBuildPairs($bug->product, 'all', 'noempty');
$openedBuilds = $this->build->getBuildPairs($bug->product, $bug->branch, $params = 'noempty,noterminate,nodone,withbranch,noreleased', $objectID, $objectType, $bug->openedBuild);
$resolvedBuilds = $openedBuilds;
if(($bug->resolvedBuild) && isset($allBuilds[$bug->resolvedBuild])) $resolvedBuilds[$bug->resolvedBuild] = $allBuilds[$bug->resolvedBuild];
/* 获取分支列表。*/
/* Get the branch options. */
if($this->app->tab == 'execution' || $this->app->tab == 'project') $objectID = $this->app->tab == 'project' ? $bug->project : $bug->execution;
$branches = $this->branch->getList($bug->product, isset($objectID) ? $objectID : 0, 'all');
$branchTagOption = array();
foreach($branches as $branchInfo) $branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
if(!isset($branchTagOption[$bug->branch]))
{
$bugBranch = $this->branch->getById($bug->branch, $bug->product, '');
if($bug->branch == BRANCH_MAIN) $branchTagName = $bugBranch;
if($bug->branch != BRANCH_MAIN)
{
$branchTagName = $bugBranch->name;
if($bugBranch->status == 'closed') $branchTagName .= " ({$this->lang->branch->statusList['closed']})";
}
$branchTagOption[$bug->branch] = $branchTagName;
}
/* 获取moduleOptionMenu。*/
/* Get moduleOptionMenu. */
$moduleOptionMenu = $this->tree->getOptionMenu($bug->product, $viewType = 'bug', $startModuleID = 0, $bug->branch);
if(!isset($moduleOptionMenu[$bug->module])) $moduleOptionMenu += $this->tree->getModulesName($bug->module);
/* 获取指派给用户列表。*/
/* Get assigned to member. */
if($bug->execution)
{
$assignedToList = $this->user->getTeamMemberPairs($bug->execution, 'execution');
}
elseif($bug->project)
{
$assignedToList = $this->project->getTeamMemberPairs($bug->project);
}
else
{
$assignedToList = $this->bug->getProductMemberPairs($bug->product, $bug->branch);
$assignedToList = array_filter($assignedToList);
if(empty($assignedToList)) $assignedToList = $this->user->getPairs('devfirst|noclosed');
}
if($bug->assignedTo and !isset($assignedToList[$bug->assignedTo]) and $bug->assignedTo != 'closed')
{
$assignedTo = $this->user->getById($bug->assignedTo);
$assignedToList[$bug->assignedTo] = $assignedTo->realname;
}
if($bug->status == 'closed') $assignedToList['closed'] = 'Closed';
/* 获取该bug关联产品和分支下的bug列表。*/
/* Get bugs of current product. */
$branch = '';
if($product->type == 'branch') $branch = $bug->branch > 0 ? "{$bug->branch},0" : '0';
$productBugs = $this->bug->getProductBugPairs($bug->product, $branch);
unset($productBugs[$bug->id]);
/* 获取执行列表。*/
/* Get execution pairs. */
$executions = array('') + $this->product->getExecutionPairsByProduct($bug->product, $bug->branch, 'id_desc', $bug->project);
if(!empty($bug->execution) and empty($executions[$bug->execution])) $executions[$execution->id] = $execution->name . "({$this->lang->bug->deleted})";
/* 获取项目列表。*/
/* Get project pairs. */
$projects = array('') + $this->product->getProjectPairsByProduct($bug->product, $bug->branch);
if(!empty($bug->project) and empty($projects[$bug->project]))
{
$project = $this->project->getByID($bug->project);
$projects[$project->id] = $project->name . "({$this->lang->bug->deleted})";
}
/* 如果产品列表没有bug相关的产品,把该产品加入产品列表。*/
/* Add product related by the bug when it is not in the products. */
if(!isset($this->products[$bug->product]))
{
$this->products[$bug->product] = $product->name;
$this->view->products = $this->products;
}
if($product->shadow) $this->view->project = $this->project->getByShadowProduct($bug->product);
$this->view->title = $this->lang->bug->edit . "BUG #$bug->id $bug->title - " . $this->products[$bug->product];
$this->view->bug = $bug;
$this->view->product = $product;
$this->view->execution = $execution;
$this->view->branchTagOption = $branchTagOption;
$this->view->moduleOptionMenu = $moduleOptionMenu;
$this->view->plans = $this->loadModel('productplan')->getPairs($bug->product, $bug->branch, '', true);
$this->view->projects = $projects;
$this->view->executions = $executions;
$this->view->projectExecutionPairs = $this->project->getProjectExecutionPairs();
$this->view->stories = $bug->execution ? $this->story->getExecutionStoryPairs($bug->execution) : $this->story->getProductStoryPairs($bug->product, $bug->branch, 0, 'all', 'id_desc', 0, 'full', 'story', false);
$this->view->tasks = $this->task->getExecutionTaskPairs($bug->execution);
$this->view->testtasks = $this->loadModel('testtask')->getPairs($bug->product, $bug->execution, $bug->testtask);
$this->view->cases = array('') + $this->loadModel('testcase')->getPairsByProduct($bug->product, array(0, $bug->branch));
$this->view->productBugs = $productBugs;
$this->view->openedBuilds = $openedBuilds;
$this->view->resolvedBuilds = array('') + $resolvedBuilds;
$this->view->users = $this->user->getPairs('', "$bug->assignedTo,$bug->resolvedBy,$bug->closedBy,$bug->openedBy");
$this->view->assignedToList = $assignedToList;
$this->view->actions = $this->action->getList('bug', $bug->id);
$this->display();
}
/**
* 如果不是弹窗,调用该方法为查看bug设置导航。
* If it's not a iframe, call this method to set menu for view bug page.
+2 -2
View File
@@ -1770,7 +1770,7 @@ class execution extends control
$this->view->linkedBranches = $linkedBranches;
}
if(!empty($project) and !$project->division)
if(!empty($project) and $project->stageBy == 'project')
{
$products = $this->loadModel('product')->getProducts($projectID);
$branches = $this->project->getBranchesByProject($projectID);
@@ -1916,7 +1916,7 @@ class execution extends control
$this->view->from = $this->app->tab;
$this->view->isStage = (isset($project->model) and ($project->model == 'waterfall' or $project->model == 'waterfallplus')) ? true : false;
$this->view->project = $project;
$this->view->division = !empty($project) ? $project->division : 1;
$this->view->stageBy = $project->stageBy;
$this->view->type = $type;
$this->display();
}
+15 -14
View File
@@ -1767,7 +1767,6 @@ class executionModel extends model
->andWhere('vision')->eq($this->config->vision)
->beginIF($type == 'all')->andWhere('type')->in('sprint,stage,kanban')->fi()
->beginIF($type != 'all')->andWhere('type')->eq($type)->fi()
->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
->beginIF($status == 'undone')->andWhere('status')->notIN('done,closed')->fi()
->beginIF($status != 'all' and $status != 'undone')->andWhere('status')->in($status)->fi()
->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->sprints)->fi()
@@ -1820,7 +1819,6 @@ class executionModel extends model
->orWhere('t2.account')->eq($this->app->user->account)
->markRight(1)
->andWhere('t1.type')->in('sprint,stage,kanban')
->beginIF($projectID)->andWhere('t1.project')->eq($projectID)->fi()
->orderBy('t1.order_desc')
->beginIF($limit)->limit($limit)->fi()
->fetchAll('id');
@@ -2054,7 +2052,7 @@ class executionModel extends model
if(isset($project->model) and in_array($project->model, array('waterfall', 'waterfallplus')))
{
$executionProducts = array();
if($project->hasProduct and $project->division)
if($project->hasProduct and ($project->stageBy == 'product'))
{
$executionList = array();
$executionProducts = $this->dao->select('t1.project, t2.name')->from(TABLE_PROJECTPRODUCT)->alias('t1')
@@ -4334,34 +4332,37 @@ class executionModel extends model
*/
public function summary($tasks)
{
$taskSum = $statusWait = $statusDone = $statusDoing = $statusClosed = $statusCancel = $statusPause = 0;
$taskSum = 0;
$totalEstimate = $totalConsumed = $totalLeft = 0.0;
$summations = array();
$this->app->loadLang('task');
/* 当前只需要显示wait 和 doing 状态,但是从代码分析将来可能需要统计其他状态的,所以取全部状态。 */
foreach($this->lang->task->statusList as $statusCode => $statusName) $summations[$statusCode] = 0;
foreach($tasks as $task)
{
if(!isset($tasks[$task->parent]) or $task->parent <= 0)
{
$totalEstimate += $task->estimate;
$totalConsumed += $task->consumed;
$totalEstimate += $task->estimate;
$totalConsumed += $task->consumed;
if($task->status != 'cancel' and $task->status != 'closed') $totalLeft += $task->left;
}
$statusVar = 'status' . ucfirst($task->status);
$$statusVar ++;
if(isset($summations[$task->status])) $summations[$task->status] ++;
if(isset($task->children))
{
foreach($task->children as $children)
foreach($task->children as $child)
{
$statusVar = 'status' . ucfirst($children->status);
$$statusVar ++;
if(isset($summations[$child->status])) $summations[$child->status] ++;
$taskSum ++;
}
}
$taskSum ++;
}
return sprintf($this->lang->execution->taskSummary, $taskSum, $statusWait, $statusDoing, round($totalEstimate, 1), round($totalConsumed, 1), round($totalLeft, 1));
return sprintf($this->lang->execution->taskSummary, $taskSum, $summations['wait'], $summations['doing'], round($totalEstimate, 1), round($totalConsumed, 1), round($totalLeft, 1));
}
/**
@@ -5266,7 +5267,7 @@ class executionModel extends model
}
}
}
if(!empty($execution->division) and $execution->hasProduct) echo "<td class='text-left' title='{$execution->productName}'>{$execution->productName}</td>";
if(($execution->stageBy == 'project') and $execution->hasProduct) echo "<td class='text-left' title='{$execution->productName}'>{$execution->productName}</td>";
echo "<td class='status-{$execution->status} text-center'>" . zget($this->lang->project->statusList, $execution->status) . '</td>';
echo '<td>' . zget($users, $execution->PM) . '</td>';
echo helper::isZeroDate($execution->begin) ? '<td class="c-date"></td>' : '<td class="c-date">' . $execution->begin . '</td>';
@@ -5321,7 +5322,7 @@ class executionModel extends model
{
foreach($execution->children as $child)
{
$child->division = $execution->division;
$child->stageBy = $execution->stageBy;
$this->printNestedList($child, true, $users, $productID);
}
}
@@ -73,7 +73,7 @@ CREATE TABLE `zt_project` (
`whitelist` text NOT NULL,
`order` mediumint(8) unsigned NOT NULL,
`vision` varchar(10) NOT NULL DEFAULT 'rnd',
`division` enum('0','1') NOT NULL DEFAULT '1',
`stageBy` enum('product','project') NOT NULL DEFAULT 'product',
`displayCards` smallint(6) NOT NULL DEFAULT 0,
`fluidBoard` enum('0','1') NOT NULL DEFAULT '0',
`multiple` enum('0','1') NOT NULL DEFAULT '1',
+4 -4
View File
@@ -157,9 +157,9 @@
<?php $hasBranch = $product->type != 'normal' and isset($branchGroups[$product->id]);?>
<div class='input-group <?php if($hasBranch) echo ' has-branch';?>'>
<span class='input-group-addon'><?php echo $lang->productCommon;?></span>
<?php $disabled = ($isStage and !$project->division) ? "disabled='disabled'" : '';?>
<?php $disabled = ($isStage and $project->stageBy == 'project') ? "disabled='disabled'" : '';?>
<?php echo html::select("products[$i]", $allProducts, $product->id, "class='form-control chosen' $disabled onchange='loadBranches(this)' data-last='" . $product->id . "' data-type='" . $product->type . "'");?>
<?php if($isStage and !$project->division) echo html::hidden("products[$i]", $product->id);?>
<?php if($isStage and $project->stageBy == 'project') echo html::hidden("products[$i]", $product->id);?>
</div>
</div>
<div class='table-col <?php if(!$hasBranch) echo 'hidden'; if($disabled) echo ' disabledBranch'?>'>
@@ -177,7 +177,7 @@
<span class='input-group-addon'><?php echo $lang->product->plan;?></span>
<?php if(empty($productID) or (!empty($productID) and $productID != $product->id)) echo html::select("plans[$product->id][]", isset($productPlans[$product->id]) ? $productPlans[$product->id] : array(), isset($product->plans) ? $product->plans : '', "class='form-control chosen' multiple");?>
<?php if(!empty($productID) and $productID == $product->id) echo html::select("plans[$product->id][]", !empty($productPlan) ? $productPlan : array(), isset($productPlan[$plan->id]) ? $plan->id : '', "class='form-control chosen' multiple");?>
<?php if(!($isStage and !$project->division)):?>
<?php if(!($isStage and $project->stageBy == 'project')):?>
<div class='input-group-btn'>
<a href='javascript:;' onclick='addNewLine(this)' class='btn btn-link addLine'><i class='icon-plus'></i></a>
<a href='javascript:;' onclick='removeLine(this)' class='btn btn-link removeLine' <?php if($i == 0) echo "style='visibility: hidden'";?>><i class='icon-close'></i></a>
@@ -225,7 +225,7 @@
<div class='input-group' id='plan0'>
<span class='input-group-addon'><?php echo $lang->product->plan;?></span>
<?php echo html::select("plans[][]", $productPlan, '', "class='form-control chosen' multiple");?>
<?php if(!($isStage and $project->division)):?>
<?php if(!($isStage and $project->stageBy == 'product')):?>
<div class='input-group-btn'>
<a href='javascript:;' onclick='addNewLine(this)' class='btn btn-link addLine'><i class='icon-plus'></i></a>
<a href='javascript:;' onclick='removeLine(this)' class='btn btn-link removeLine' style='visibility: hidden'><i class='icon-close'></i></a>
+4 -4
View File
@@ -169,9 +169,9 @@
<?php $hasBranch = $product->type != 'normal' and isset($branchGroups[$product->id]);?>
<div class='input-group <?php if($hasBranch) echo ' has-branch';?>'>
<span class='input-group-addon'><?php echo $lang->productCommon;?></span>
<?php $disabled = ($execution->type == 'stage' and !$execution->division) ? "disabled='disabled'" : '';?>
<?php $disabled = ($execution->type == 'stage' and $execution->stageBy == 'project') ? "disabled='disabled'" : '';?>
<?php echo html::select("products[$i]", $allProducts, $product->id, "class='form-control chosen' $disabled onchange='loadBranches(this)' data-last='" . $product->id . "' data-type='" . $product->type . "'");?>
<?php if($execution->type == 'stage' and !$execution->division) echo html::hidden("products[$i]", $product->id);?>
<?php if($execution->type == 'stage' and $execution->stageBy == 'project') echo html::hidden("products[$i]", $product->id);?>
</div>
</div>
<div class='table-col <?php if(!$hasBranch) echo 'hidden';?>'>
@@ -187,7 +187,7 @@
<div class='input-group' <?php echo "id='plan$i'";?>>
<span class='input-group-addon'><?php echo $lang->product->plan;?></span>
<?php echo html::select("plans[$product->id][]", isset($productPlans[$product->id]) ? $productPlans[$product->id] : array(), $product->plans, "class='form-control chosen' multiple");?>
<?php if(!($execution->type == 'stage' and !$execution->division)):?>
<?php if(!($execution->type == 'stage' and $execution->stageBy == 'project')):?>
<div class='input-group-btn'>
<a href='javascript:;' onclick='addNewLine(this)' class='btn btn-link addLine'><i class='icon-plus'></i></a>
<a href='javascript:;' onclick='removeLine(this)' class='btn btn-link removeLine' <?php if($i == 0) echo "style='visibility: hidden'";?>><i class='icon-close'></i></a>
@@ -262,7 +262,7 @@
<span class='input-group-addon'><?php echo $lang->productCommon;?></span>
<?php $disabled = ($project->model == 'waterfall' or $project->model == 'waterfallplus') ? "disabled='disabled'" : '';?>
<?php echo html::select("products[$i]", $allProducts, $product->id, "class='form-control chosen' $disabled onchange='loadBranches(this)' data-last='" . $product->id . "' data-type='" . $product->type . "'");?>
<?php if($execution->type == 'stage' and !$project->division) echo html::hidden("products[$i]", $product->id);?>
<?php if($execution->type == 'stage' and $project->stageBy == 'product') echo html::hidden("products[$i]", $product->id);?>
</div>
</div>
<div class='table-col <?php if(!$hasBranch) echo 'hidden'; if($disabled) echo ' disabledBranch'?>'>
+17 -7
View File
@@ -163,6 +163,9 @@ class product extends control
$storyBugs = $this->loadModel('bug')->getStoryBugCounts($storyIdList);
$storyCases = $this->loadModel('testcase')->getStoryCaseCounts($storyIdList);
/* Save session. */
$this->productZen->saveSession4Browse($product, $storyType, $browseType, $isProjectStory);
/* Build search form. */
$this->productZen->buildSearchForm4Browse($project, $projectID, $productID, $branch, $param, $storyType, $browseType, $isProjectStory);
@@ -423,6 +426,7 @@ class product extends control
}
/**
* 产品路线图。
* Road map of a product.
*
* @param int $productID
@@ -430,22 +434,28 @@ class product extends control
* @access public
* @return void
*/
public function roadmap($productID, $branch = 'all')
public function roadmap(string $productID, string $branch = 'all'): void
{
$productID = (int)$productID;
/* Set env viriables. */
$this->product->setMenu($productID, $branch);
$this->productZen->saveSession4Roadmap();
$this->session->set('releaseList', $this->app->getURI(true), 'product');
$this->session->set('productPlanList', $this->app->getURI(true), 'product');
$product = $this->dao->findById($productID)->from(TABLE_PRODUCT)->fetch();
/* Generate data. */
$product = $this->product->getByID($productID);
if(empty($product)) $this->locate($this->createLink('product', 'showErrorNone', 'fromModule=product'));
$roadmaps = $this->product->getRoadmap($productID, $branch);
$branches = $product->type == 'normal' ? array(0 => '') : $this->loadModel('branch')->getPairs($productID);
/* Assign view data. */
$this->view->title = $product->name . $this->lang->colon . $this->lang->product->roadmap;
$this->view->position[] = html::a($this->createLink($this->moduleName, 'browse'), $product->name);
$this->view->position[] = $this->lang->product->roadmap;
$this->view->product = $product;
$this->view->roadmaps = $this->product->getRoadmap($productID, $branch);
$this->view->branches = $product->type == 'normal' ? array(0 => '') : $this->loadModel('branch')->getPairs($productID);
$this->view->roadmaps = $roadmaps;
$this->view->branches = $branches;
$this->display();
}
+4 -4
View File
@@ -73,7 +73,6 @@ class productModel extends model
*/
public function getByID(int $productID): object|false
{
$productID = (int)$productID;
if(defined('TUTORIAL')) return $this->loadModel('tutorial')->getProduct();
$product = $this->dao->findById($productID)->from(TABLE_PRODUCT)->fetch();
if(!$product) return false;
@@ -311,7 +310,7 @@ class productModel extends model
}
else
{
$products = $this->getList('', $status, $num, 0, $shadow);
$products = $this->getList(0, $status, $num, 0, $shadow);
}
if(empty($products)) return $products;
@@ -1122,15 +1121,16 @@ class productModel extends model
}
/**
* 获取产品路线图数据。
* Get roadmap of a proejct.
*
* @param int $productID
* @param int $branch
* @param string $branch all|0|1
* @param int $count
* @access public
* @return array
*/
public function getRoadmap($productID, $branch = 0, $count = 0)
public function getRoadmap(int $productID, string $branch = '0', int $count = 0): array
{
$plans = $this->loadModel('productplan')->getList($productID, $branch);
$releases = $this->loadModel('release')->getList($productID, $branch);
+3 -3
View File
@@ -699,14 +699,14 @@ class productTao extends productModel
* Get products by project ID.
*
* @param int $projectID
* @param array $views
* @param string $productIdListStr '1,2,3'
* @param string $status
* @param string $orderBy
* @param bool $noDeleted
* @access protected
* @return int
*/
protected function getProductsByProjectID(int $projectID, array $views, string $status, string $orderBy, bool $noDeleted): array
protected function getProductsByProjectID(int $projectID, string $productIdListStr, string $status, string $orderBy, bool $noDeleted): array
{
return $this->dao->select("t1.branch, t1.plan, t2.*")
->from(TABLE_PROJECTPRODUCT)->alias('t1')
@@ -714,7 +714,7 @@ class productTao extends productModel
->where('1=1')
->beginIF($noDeleted)->andWhere('t2.deleted')->eq(0)->fi()
->beginIF(!empty($projectID))->andWhere('t1.project')->in($projectID)->fi()
->beginIF(!$this->app->user->admin and $this->config->vision == 'rnd')->andWhere('t2.id')->in($views)->fi()
->beginIF(!$this->app->user->admin and $this->config->vision == 'rnd')->andWhere('t2.id')->in($productIdListStr)->fi()
->andWhere('t2.vision')->eq($this->config->vision)
->beginIF(strpos($status, 'noclosed') !== false)->andWhere('t2.status')->ne('closed')->fi()
->orderBy($orderBy . 't2.order asc')
+44 -11
View File
@@ -841,14 +841,10 @@ class productZen extends product
{
if($this->app->tab == 'project')
{
$this->session->set('storyList', $this->app->getURI(true), 'project');
$this->loadModel('project')->setMenu($projectID);
return;
}
$this->session->set('storyList', $this->app->getURI(true), 'product');
$this->session->set('productList', $this->app->getURI(true), 'product');
$this->product->setMenu($productID, $branch, 0, '', "storyType=$storyType");
}
@@ -1047,8 +1043,6 @@ class productZen extends product
/* Get stories. */
if($isProjectStory and $storyType == 'story')
{
if(!empty($product)) $this->session->set('currentProductType', $product->type);
$this->products = $this->product->getProducts($projectID, 'all', '', false);
if($browseType == 'bybranch') $param = $branchID;
@@ -1062,7 +1056,7 @@ class productZen extends product
if(!empty($stories)) $stories = $this->story->mergeReviewer($stories);
return [$stories, $pager];
return array($stories, $pager);
}
/**
@@ -1120,6 +1114,36 @@ class productZen extends product
return [$branchOption, $branchTagOption];
}
/**
* 保存需求页面session变量。
* Save session variables for browse page.
*
* @param object $product
* @param string $storyType
* @param string $browseType
* @param bool $isProjectStory
* @access private
* @return void
*/
protected function saveSession4Browse(object $product, string $storyType, string $browseType, bool $isProjectStory): void
{
$uri = $this->app->getURI(true);
/* For setMenu. */
if($this->app->tab == 'project') $this->session->set('storyList', $uri, 'project');
else {
$this->session->set('productList', $uri, 'product');
$this->session->set('storyList', $uri, 'product');
}
/* For getStoriesAndPager. */
if($isProjectStory && $storyType == 'story' && !empty($product)) $this->session->set('currentProductType', $product->type);
/* Save browse type into session for buildSearchForm. */
if($browseType != 'bymodule' && $browseType != 'bybranch') $this->session->set('storyBrowseType', $browseType);
if(($browseType == 'bymodule' || $browseType == 'bybranch') && $this->session->storyBrowseType == 'bysearch') $this->session->set('storyBrowseType', 'unclosed');
}
/**
* 构建搜索表单。
* Build search form.
@@ -1137,10 +1161,6 @@ class productZen extends product
*/
protected function buildSearchForm4Browse(object|bool $project,int $projectID, int &$productID, string $branch, int $param, string $storyType, string $browseType, bool $isProjectStory): void
{
/* Save browse type into session. */
if($browseType != 'bymodule' and $browseType != 'bybranch') $this->session->set('storyBrowseType', $browseType);
if(($browseType == 'bymodule' or $browseType == 'bybranch') and $this->session->storyBrowseType == 'bysearch') $this->session->set('storyBrowseType', 'unclosed');
/* Change for requirement story title. */
if($storyType == 'requirement')
{
@@ -1189,4 +1209,17 @@ class productZen extends product
return $storyIdList;
}
/**
* 路线图页面保存session数据。
* Save session variables data.
*
* @access protected
* @return void
*/
protected function saveSession4Roadmap(): void
{
$this->session->set('releaseList', $this->app->getURI(true), 'product');
$this->session->set('productPlanList', $this->app->getURI(true), 'product');
}
}
+15 -72
View File
@@ -18,14 +18,13 @@ class programplan extends control
*
* @param int $projectID
* @param int $productID
* @param string $extra
* @access public
* @return void
*/
public function commonAction($projectID, $productID = 0, $extra = '')
public function commonAction(int $projectID, int $productID = 0)
{
$products = $this->loadModel('product')->getProductPairsByProject($projectID);
$productID = $this->product->saveVisitState($productID, $products);
$productID = $this->loadModel('product')->saveVisitState($productID, $products);
$project = $this->loadModel('project')->getByID($projectID);
$this->session->set('hasProduct', $project->hasProduct);
@@ -33,91 +32,35 @@ class programplan extends control
}
/**
* 渲染阶段数据页面。
* Browse program plans.
*
* @param int $projectID
* @param int $productID
* @param string $projectID
* @param string $productID
* @param string $type
* @param string $orderBy
* @param int $baselineID
* @param string $baselineID
* @access public
* @return void
*/
public function browse($projectID = 0, $productID = 0, $type = 'gantt', $orderBy = 'id_asc', $baselineID = 0)
public function browse(string $projectID = '0', string $productID = '0', string $type = 'gantt', string $orderBy = 'id_asc', string $baselineID = '0')
{
$productID = (int)$productID;
$projectID = (int)$projectID;
$baselineID = (int)$baselineID;
$this->app->loadLang('stage');
$this->commonAction($projectID, $productID, $type);
$this->session->set('projectPlanList', $this->app->getURI(true), 'project');
$this->commonAction($projectID, $productID);
if(!defined('RUN_MODE') || RUN_MODE != 'api') $projectID = $this->project->saveState((int)$projectID, $this->project->getPairsByProgram());
if(!defined('RUN_MODE') || RUN_MODE != 'api') $projectID = $this->project->saveState($projectID, $this->project->getPairsByProgram());
$products = $this->loadModel('product')->getProducts($projectID);
if($this->session->hasProduct) $this->lang->modulePageNav = $this->product->select($products, $productID, 'programplan', 'browse', $type, 0, 0, '', false);
$selectCustom = 0; // Display date and task settings.
$dateDetails = 1; // Gantt chart detail date display.
if($type == 'gantt')
{
$this->loadModel('setting');
$owner = $this->app->user->account;
$module = 'programplan';
$section = 'browse';
$object = 'stageCustom';
if(!isset($this->config->programplan->browse->stageCustom)) $this->setting->setItem("$owner.$module.browse.stageCustom", 'date,task');
/* 生成阶段列表页阶段数据。 */
$stages = $this->programplanZen->buildBrowseStages($projectID, $productID, $baselineID, $type, $orderBy);
$selectCustom = $this->setting->getItem("owner={$owner}&module={$module}&section={$section}&key={$object}");
if(strpos($selectCustom, 'date') !== false) $dateDetails = 0;
$plans = $this->programplan->getDataForGantt($projectID, $productID, $baselineID, $selectCustom, false);
/* Set Custom. */
foreach(explode(',', $this->config->programplan->custom->customGanttFields) as $field) $customFields[$field] = $this->lang->programplan->ganttCustom[$field];
$this->view->customFields = $customFields;
$this->view->showFields = $this->config->programplan->ganttCustom->ganttFields;
}
if($type == 'assignedTo')
{
$owner = $this->app->user->account;
$module = 'programplan';
$section = 'browse';
$object = 'stageCustom';
$selectCustom = $this->loadModel('setting')->getItem("owner={$owner}&module={$module}&section={$section}&key={$object}");
if(strpos($selectCustom, 'date') !== false) $dateDetails = 0;
$plans = $this->programplan->getDataForGanttGroupByAssignedTo($projectID, $productID, $baselineID, $selectCustom, false);
/* Set Custom. */
foreach(explode(',', $this->config->programplan->custom->customGanttFields) as $field) $customFields[$field] = $this->lang->programplan->ganttCustom[$field];
$this->view->customFields = $customFields;
$this->view->showFields = $this->config->programplan->ganttCustom->ganttFields;
}
if($type == 'lists')
{
$sort = common::appendOrder($orderBy);
$this->loadModel('datatable');
$plans = $this->programplan->getPlans($projectID, $productID, $sort);
}
$zooming = !empty($this->config->programplan->ganttCustom->zooming) ? $this->config->programplan->ganttCustom->zooming : 'day';
$this->view->title = $this->lang->programplan->browse;
$this->view->position[] = $this->lang->programplan->browse;
$this->view->projectID = $projectID;
$this->view->project = $this->project->getByID($projectID);
$this->view->productID = $productID;
$this->view->product = $this->product->getByID($productID);
$this->view->productList = $this->product->getProductPairsByProject($projectID, 'all', '', false);
$this->view->type = $type;
$this->view->plans = $plans;
$this->view->orderBy = $orderBy;
$this->view->selectCustom = $selectCustom;
$this->view->dateDetails = $dateDetails;
$this->view->users = $this->loadModel('user')->getPairs('noletter');
$this->view->zooming = $zooming;
$this->view->ganttType = $type;
$this->display();
$this->programplanZen->buildBrowseView($projectID, $productID, $stages, $type, $orderBy);
}
/**
+36 -28
View File
@@ -146,9 +146,9 @@ class programplanModel extends model
* @param string $selectCustom
* @param bool $returnJson
* @access public
* @return string
* @return string|array
*/
public function getDataForGantt(int $executionID, int $productID, int $baselineID = 0, string $selectCustom = '', bool $returnJson = true): string
public function getDataForGantt(int $executionID, int $productID, int $baselineID = 0, string $selectCustom = '', bool $returnJson = true): string|array
{
$this->loadModel('stage');
$this->loadModel('execution');
@@ -873,7 +873,7 @@ class programplanModel extends model
$linkProducts = array();
$linkBranches = array();
$productList = $this->loadModel('product')->getProducts($projectID);
if($project->division)
if($project->stageBy == 'product')
{
$linkProducts = array(0 => $productID);
$linkBranches = array(0 => $productList[$productID]->branches);
@@ -954,7 +954,7 @@ class programplanModel extends model
{
unset($plan->id);
$plan->status = 'wait';
$plan->division = $project->division;
$plan->stageBy = $project->stageBy;
$plan->version = 1;
$plan->parentVersion = $plan->parent == 0 ? 0 : $this->dao->findByID($plan->parent)->from(TABLE_PROJECT)->fetch('version');
$plan->team = substr($plan->name,0, 30);
@@ -1093,16 +1093,18 @@ class programplanModel extends model
* @access public
* @return bool|array
*/
public function update(int $planID = 0, int $projectID = 0, object $plan): bool|array
public function update(int $planID = 0, int $projectID = 0, object $plan = new stdClass()): bool|array
{
$oldPlan = $this->getByID($planID);
/* 判断提交数据是否正确。 */
/* Judgment of required items. */
if(!$this->checkRequiredItems($oldPlan, $plan, $projectID)) return false;
$setCode = (isset($this->config->setCode) and $this->config->setCode == 1) ? true : false;
$planChanged = ($oldPlan->name != $plan->name || $oldPlan->milestone != $plan->milestone || $oldPlan->begin != $plan->begin || $oldPlan->end != $plan->end);
/* 设置计划和真实起始日期间隔时间。 */
/* Set planDuration and realDuration. */
if($this->config->edition == 'max')
{
@@ -1122,14 +1124,14 @@ class programplanModel extends model
$relatedExecutionsID = !empty($relatedExecutionsID) ? implode(',', array_keys($relatedExecutionsID)) : '0';
$conditions = array();
$conditions['requiredFields'] = $this->config->programplan->edit->requiredFields;
$conditions['requiredFields'] = $this->config->programplan->edit->requiredFields ?? '';
$conditions['relatedExecutionsID'] = $relatedExecutionsID;
$conditions['oldProject'] = $oldPlan->project;
$conditions['oldParent'] = $oldPlan->parent;
$conditions['parentStage'] = $parentStage;
$conditions['setCode'] = $setCode;
$result = $this->programplanTao->update($plan, $conditions);
$result = $this->programplanTao->updateRow($plan, $conditions);
if(!$result) return false;
$this->setTreePath($planID);
@@ -1469,31 +1471,33 @@ class programplanModel extends model
* @access public
* @return void
*/
public function computeProgress($stageID, $action = '', $isParent = false)
public function computeProgress(int $stageID, string $action = '', bool $isParent = false)
{
$stage = $this->loadModel('execution')->getByID($stageID);
$project = $this->loadModel('project')->getByID($stage->project);
if(empty($stage) or empty($stage->path) or ($project->model != 'waterfall' and $project->model != 'waterfallplus')) return false;
$this->loadModel('execution');
$this->loadModel('project');
$this->loadModel('action');
$stage = $this->execution->getByID($stageID);
$project = $this->project->getByID($stage->project);
if(empty($stage) || empty($stage->path) || ($project->model != 'waterfall' && $project->model != 'waterfallplus')) return false;
$action = strtolower($action);
$parentIdList = explode(',', trim($stage->path, ','));
$parentIdList = array_reverse($parentIdList);
foreach($parentIdList as $id)
{
$parent = $this->execution->getByID($id);
if(empty($this->lang->execution->typeList[$parent->type]) or (!$isParent and $id == $stageID)) continue;
if(empty($this->lang->execution->typeList[$parent->type]) || (!$isParent && $id == $stageID)) continue;
$statusCount = array();
$children = $this->execution->getChildExecutions($parent->id);
$allChildren = $this->dao->select('id')->from(TABLE_EXECUTION)->where('deleted')->eq(0)->andWhere('path')->like("{$parent->path}%")->andWhere('id')->ne($id)->fetchPairs();
$startTasks = $this->dao->select('count(1) as count')->from(TABLE_TASK)->where('deleted')->eq(0)->andWhere('execution')->in($allChildren)->andWhere('consumed')->ne(0)->fetch('count');
foreach($children as $childID => $childExecution) $statusCount[$childExecution->status] = empty($statusCount[$childExecution->status]) ? 1 : $statusCount[$childExecution->status] ++;
foreach($children as $childExecution) $statusCount[$childExecution->status] = empty($statusCount[$childExecution->status]) ? 1 : $statusCount[$childExecution->status] ++;
if(empty($statusCount)) continue;
if(isset($statusCount['wait']) and count($statusCount) == 1 and helper::isZeroDate($parent->realBegan) and $startTasks == 0)
$count = count($statusCount);
if(isset($statusCount['wait']) && $count == 1 && helper::isZeroDate($parent->realBegan) && $startTasks == 0)
{
if($parent->status != 'wait')
{
@@ -1501,7 +1505,7 @@ class programplanModel extends model
$parentAction = 'waitbychild';
}
}
elseif(isset($statusCount['closed']) and count($statusCount) == 1)
elseif(isset($statusCount['closed']) && $count == 1)
{
if($parent->status != 'closed')
{
@@ -1509,7 +1513,7 @@ class programplanModel extends model
$parentAction = 'closedbychild';
}
}
elseif(isset($statusCount['suspended']) and (count($statusCount) == 1 or (isset($statusCount['closed']) and count($statusCount) == 2)))
elseif(isset($statusCount['suspended']) && ($count == 1 || (isset($statusCount['closed']) && $count == 2)))
{
if($parent->status != 'suspended')
{
@@ -1536,13 +1540,14 @@ class programplanModel extends model
}
/**
* 检查阶段是否是叶子阶段。
* Check if the stage is a leaf stage.
*
* @param int $planID
* @access public
* @return bool
*/
public function checkLeafStage($planID)
public function checkLeafStage(int $planID): bool
{
$subStageList = $this->dao->select('id')->from(TABLE_EXECUTION)->where('parent')->eq($planID)->andWhere('deleted')->eq(0)->fetchAll('id');
@@ -1662,17 +1667,19 @@ class programplanModel extends model
*/
private function checkRequiredItems(object $oldPlan, object $plan, int $projectID): bool
{
/* 校验开始结束时间是否正确。 */
/* check begin and end date. */
if($plan->begin == '0000-00-00') dao::$errors['begin'][] = sprintf($this->lang->error->notempty, $this->lang->programplan->begin);
if($plan->end == '0000-00-00') dao::$errors['end'][] = sprintf($this->lang->error->notempty, $this->lang->programplan->end);
if(dao::isError()) return false;
if($plan->parent) $parentStage = $this->getByID($plan->parent);
if(isset($parentStage) and $plan->begin < $parentStage->begin)
if(isset($parentStage) && $plan->begin < $parentStage->begin)
{
dao::$errors['begin'] = sprintf($this->lang->programplan->error->letterParent, $parentStage->begin);
return false;
}
if(isset($parentStage) and $plan->end > $parentStage->end)
if(isset($parentStage) && $plan->end > $parentStage->end)
{
dao::$errors['end'] = sprintf($this->lang->programplan->error->greaterParent, $parentStage->end);
return false;
@@ -1681,30 +1688,30 @@ class programplanModel extends model
if($projectID) $this->loadModel('execution')->checkBeginAndEndDate($projectID, $plan->begin, $plan->end);
if(dao::isError()) return false;
$setCode = (isset($this->config->setCode) and $this->config->setCode == 1) ? true : false;
if($setCode and empty($plan->code))
$setCode = (isset($this->config->setCode) && $this->config->setCode == 1) ? true : false;
if($setCode && empty($plan->code))
{
dao::$errors['code'][] = sprintf($this->lang->error->notempty, $this->lang->execution->code);
return false;
}
$setPercent = isset($this->config->setPercent) and $this->config->setPercent == 1 ? true : false;
/* 对于是否有子阶段进行判断处理。 */
/* check parent stage. */
$setPercent = isset($this->config->setPercent) && $this->config->setPercent == 1 ? true : false;
if($plan->parent > 0)
{
$plan->attribute = $parentStage->attribute == 'mix' ? $plan->attribute : $parentStage->attribute;
$plan->acl = $parentStage->acl;
if($setPercent)
{
$parentPercent = $parentStage->percent;
$childrenTotalPercent = $this->getTotalPercent($parentStage, true);
$childrenTotalPercent = $plan->parent == $oldPlan->parent ? ($childrenTotalPercent - $oldPlan->percent + $plan->percent) : ($childrenTotalPercent + $plan->percent);
if($childrenTotalPercent > 100) return dao::$errors['percent'][] = $this->lang->programplan->error->percentOver;
}
/* 如果子阶段有里程碑,那么父阶段的更新为0。 */
/* If child plan has milestone, update parent plan set milestone eq 0 . */
if($plan->milestone and $parentStage->milestone) $this->dao->update(TABLE_PROJECT)->set('milestone')->eq(0)->where('id')->eq($oldPlan->parent)->exec();
if($plan->milestone && $parentStage->milestone) $this->dao->update(TABLE_PROJECT)->set('milestone')->eq(0)->where('id')->eq($oldPlan->parent)->exec();
}
else
{
@@ -1712,6 +1719,7 @@ class programplanModel extends model
$childrenIDList = $this->dao->select('id')->from(TABLE_PROJECT)->where('parent')->eq($oldPlan->id)->fetchAll('id');
if(!empty($childrenIDList)) $this->dao->update(TABLE_PROJECT)->set('acl')->eq($plan->acl)->where('id')->in(array_keys($childrenIDList))->exec();
/* 父阶段不能那个超过100。 */
/* The workload of the parent plan cannot exceed 100%. */
$oldPlan->parent = $plan->parent;
if($setPercent)
@@ -1,23 +1,16 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 5) . '/test/lib/init.php';
include dirname(__FILE__, 2) . '/programplan.class.php';
zdTable('user')->gen(5);
function initData()
{
zdTable('user')->gen(5);
zdTable('project')->config('checkleafstage')->gen(5);
}
su('admin');
$execution = zdTable('project');
$execution->id->range('1-5');
$execution->name->range('瀑布项目1,阶段a,阶段a子1,阶段a子1子1,阶段b');
$execution->type->range('project,stage{4}');
$execution->project->range('0,1{4}');
$execution->parent->range('0,1,2,3,1');
$execution->path->range("`,1,`,`,1,2,`,`,1,2,3,`,`,1,2,3,4,`,`,1,5,`");
$execution->status->range('doing,doing,doing,closed,suspended');
$execution->openedBy->range('admin,user1');
$execution->begin->range('20220112 000000:0')->type('timestamp')->format('YY/MM/DD');
$execution->end->range('20220212 000000:0')->type('timestamp')->format('YY/MM/DD');
$execution->realBegan->range('20220212 000000:0')->type('timestamp')->format('YY/MM/DD');
$execution->gen(5);
/**
@@ -25,8 +18,9 @@ title=测试programplanModel->checkLeafStage();
cid=1
pid=1
测试id为2判断是否为叶子节点 >> 0
测试id为5判断是否为叶子节点 >> 1
- 测试id为2判断是否为叶子节点 @0
- 测试id为5判断是否为叶子节点 @1
*/
@@ -0,0 +1,33 @@
title: programplanModel::checkLeafStage UTC.
desc: programplanModel::checkLeafStage UTC.
author: xushenjie
version: '1.0'
fields:
- field: name
range: '瀑布项目1,阶段a,阶段a子1,阶段a子1子1,阶段b'
- field: type
range: '0,1{4}'
- field: project
range: '0,1{4}'
- field: parent
range: '0,1,2,3,1'
- field: grade
range: 1
- field: path
range: "`,1,`,`,1,2,`,`,1,2,3,`,`,1,2,3,4,`,`,1,5,`"
- field: status
range: 'doing,doing,doing,closed,suspended'
- field: openedBy
range: 'admin,user1'
- field: begin
range: '20230501 000000:0'
type: 'timestamp'
format: 'YY/MM/DD'
- field: end
range: '20300501 000000:0'
type: 'timestamp'
format: 'YY/MM/DD'
- field: realBegan
range: '20230501 000000:0'
type: 'timestamp'
format: 'YY/MM/DD'
+1 -1
View File
@@ -14,7 +14,7 @@
<style>#dropMenu{z-index:99999;}</style>
<?php js::set('browseType', $type);?>
<div class='main-table'>
<?php if(empty($plans)):?>
<?php if(empty($stages)):?>
<div class="table-empty-tip">
<p>
<span class="text-muted"><?php echo $lang->programplan->noData;?></span>
+1 -1
View File
@@ -23,7 +23,7 @@
echo "<span class='text'>{$title}</span>";
?>
</span>
<?php if($productList and $project->division):?>
<?php if($productList and $project->stageBy == 'product'):?>
<div class='btn-group'>
<?php $viewName = $productID != 0 ? zget($productList,$productID) : current($productList);?>
<a href='javascript:;' class='btn btn-link btn-limit' data-toggle='dropdown'><span class='text' title='<?php echo $viewName;?>'><?php echo $viewName;?></span> <span class='caret'></span></a>
+3 -3
View File
@@ -86,7 +86,7 @@ form {display: block; margin-top: 0em; margin-block-end: 1em;}
<?php if($this->app->getModuleName() == 'programplan'):?>
<div class='btn-toolbar pull-left'>
<div class='btn-group'>
<?php if(!empty($project->division)):?>
<?php if($project->stageBy == 'product'):?>
<?php $viewName = $productID != 0 ? zget($productList,$productID) : $lang->product->allProduct;?>
<a href='javascript:;' class='btn btn-link btn-limit text-ellipsis' data-toggle='dropdown' style="max-width: 120px;"><span class='text' title='<?php echo $viewName;?>'><?php echo $viewName;?></span> <span class='caret'></span></a>
<ul class='dropdown-menu' style='max-height:240px; max-width: 300px; overflow-y:auto'>
@@ -243,7 +243,7 @@ function drawGanttToCanvas(exportType, successCallback, errorCallback)
var $ganttDridData = $ganttView.find('.gantt_grid_data');
var ganttRowHeight = $ganttView.find('.gantt_row').first().outerHeight() || 25;
var ganttHeight = $ganttView.find('.gantt_grid_scale').outerHeight() + <?php echo count($plans['data'])?> * ganttRowHeight;
var ganttHeight = $ganttView.find('.gantt_grid_scale').outerHeight() + <?php echo count($stages['data'])?> * ganttRowHeight;
<?php if($selectCustom == 'task'):?>
var ganttWidth = $ganttDridData.width() - 100;
<?php else:?>
@@ -545,7 +545,7 @@ $(function()
$('#ganttView').css('height', Math.max(200, Math.floor($(window).height() - $('#footer').outerHeight() - $('#header').outerHeight() - $('#mainMenu').outerHeight() - 120)));
};
var ganttData = $.parseJSON(<?php echo json_encode(json_encode($plans));?>);
var ganttData = $.parseJSON(<?php echo json_encode(json_encode($stages));?>);
if(!ganttData.data) ganttData.data = [];
<?php
+1 -1
View File
@@ -55,7 +55,7 @@ js::set('confirmDelete', $lang->programplan->confirmDelete);
</tr>
</thead>
<tbody>
<?php foreach($plans as $plan):?>
<?php foreach($stages as $plan):?>
<tr data-id='<?php echo $plan->id?>'>
<?php foreach($setting as $key => $value) $this->programplan->printCell($value, $plan, $users, $projectID);?>
</tr>
+78 -3
View File
@@ -20,9 +20,8 @@ class programplanZen extends programplan
*/
protected function beforeEdit(object $formData): object
{
$rowData = $formData->rowdata;
$plan = $formData->join($rowData->output, ',')->get();
return $plan;
$rowData = $formData->rawdata;
return isset($rowData->output) ? $formData->join($rowData->output, ',')->get() : $rowData;
}
/**
@@ -162,4 +161,80 @@ class programplanZen extends programplan
return array($visibleFields, $requiredFields, $customFields, $showFields);
}
/**
* 生成阶段列表页阶段数据。
* Build gantt browse stage view data.
*
* @param int $projectID
* @param int $productID
* @param array $plans
* @param string $projectID
* @param string $projectID
* @access protected
* @return array
*/
protected function buildBrowseStages($projectID, $productID, $baselineID, $type, $orderBy): array
{
$stages = array();
$selectCustom = 0; // Display date and task settings.
$dateDetails = 1; // Gantt chart detail date display.
if($type == 'lists')
{
$sort = common::appendOrder($orderBy);
$this->loadModel('datatable');
$stages = $this->programplan->getPlans($projectID, $productID, $sort);
$this->view->dateDetails = $dateDetails;
return $stages;
}
$owner = $this->app->user->account;
if(!isset($this->config->programplan->browse->stageCustom)) $this->loadModel('setting')->setItem("$owner.programplan.browse.stageCustom", 'date,task');
$selectCustom = $this->loadModel('setting')->getItem("owner={$owner}&module=programplan&section=browse&key=stageCustom");
$dateDetails = strpos($selectCustom, 'date') !== false ? 0 : 1; // Gantt chart detail date display.
/* Set Custom. */
foreach(explode(',', $this->config->programplan->custom->customGanttFields) as $field) $customFields[$field] = $this->lang->programplan->ganttCustom[$field];
$this->view->customFields = $customFields;
$this->view->showFields = $this->config->programplan->ganttCustom->ganttFields;
$this->view->dateDetails = $dateDetails;
$this->view->selectCustom = $selectCustom;
if($type == 'gantt' ) $stages = $this->programplan->getDataForGantt($projectID, $productID, $baselineID, $selectCustom, false);
if($type == 'assignedTo') $stages = $this->programplan->getDataForGanttGroupByAssignedTo($projectID, $productID, $baselineID, $selectCustom, false);
return $stages;
}
/**
* 生成gantt图视图数据。
* Build gantt browse view.
*
* @param int $projectID
* @param int $productID
* @param array $stages
* @param string $projectID
* @param string $projectID
* @access protected
* @return void
*/
protected function buildBrowseView(int $projectID, int $productID, array $stages, string $type, string $orderBy): void
{
$this->view->title = $this->lang->programplan->browse;
$this->view->projectID = $projectID;
$this->view->productID = $productID;
$this->view->type = $type;
$this->view->ganttType = $type;
$this->view->stages = $stages;
$this->view->orderBy = $orderBy;
$this->view->project = $this->loadModel('project')->getByID($projectID);
$this->view->users = $this->loadModel('user')->getPairs('noletter');
$this->view->product = $this->loadModel('product')->getByID($productID);
$this->view->productList = $this->loadModel('product')->getProductPairsByProject($projectID, 'all', '', false);
$this->view->zooming = !empty($this->config->programplan->ganttCustom->zooming) ? $this->config->programplan->ganttCustom->zooming : 'day';;
$this->display();
}
}
+21 -33
View File
@@ -221,52 +221,44 @@ class project extends control
}
/**
* 获取项目选中项目或项目集对象的信息
* Ajax: Get selected object's information.
*
* @param str $objectType
* @param int $objectID
* @param int $selectedProgramID
* @param string $objectType
* @param string $objectID
* @param string $selectedProgramID
*
* @access public
* @return void
*/
public function ajaxGetObjectInfo($objectType, $objectID, $selectedProgramID)
public function ajaxGetObjectInfo(string $objectType, string $objectID, string $selectedProgramID)
{
/* Get the available budget for the program. */
if($selectedProgramID)
{
$selectedProgram = $this->loadModel('program')->getByID($selectedProgramID);
$selectedProgramID = (int)$selectedProgramID;
$selectedProgram = $this->loadModel('program')->getByID($selectedProgramID);
if($selectedProgram->budget) $availableBudget = $this->program->getBudgetLeft($selectedProgram);
}
/* Get the available budget and program time range. */
if(!empty($objectID))
{
$objectID = (int)$objectID;
$object = $objectType == 'project' ? $this->project->getByID($objectID) : $this->loadModel('program')->getByID($objectID);
$object = $objectType == 'project' ? $this->project->getByID($objectID) : $this->loadModel('program')->getByID($objectID);
if(isset($availableBudget)) $availableBudget = $object->parent == $selectedProgramID ? $availableBudget + (int)$object->budget : $availableBudget;
if($objectType == 'program')
{
$minChildBegin = $this->dao->select('`begin` as minBegin')->from(TABLE_PROGRAM)->where('id')->ne($objectID)->andWhere('deleted')->eq(0)->andWhere('path')->like("%,{$objectID},%")->orderBy('begin_asc')->fetch('minBegin');
$maxChildEnd = $this->dao->select('`end` as maxEnd')->from(TABLE_PROGRAM)->where('id')->ne($objectID)->andWhere('deleted')->eq(0)->andWhere('path')->like("%,{$objectID},%")->andWhere('end')->ne('0000-00-00')->orderBy('end_desc')->fetch('maxEnd');
$minChildBegin = $this->project->getProgramMinBegin($objectID);
$maxChildEnd = $this->project->getProgramMaxEnd($objectID);
}
}
/* Build the selected drop-down data. */
$data = array();
if(isset($selectedProgram))
{
$data['selectedProgramBegin'] = $selectedProgram->begin;
$data['selectedProgramEnd'] = $selectedProgram->end;
$data['budgetUnit'] = $selectedProgram->budgetUnit;
$data['selectedProgramPath'] = explode(',', $selectedProgram->path);
}
if($objectType == 'program')
{
$withProgram = $this->config->systemMode == 'ALM' ? true : false;
$allProducts = array(0 => '') + $this->program->getProductPairs($selectedProgramID, 'all', 'noclosed', '', 0, $withProgram);
$data['allProducts'] = html::select("products[]", $allProducts, '', "class='form-control chosen' onchange='loadBranches(this)'");
$data['plans'] = html::select('plans[][][]', '', '', 'class=\'form-control chosen\' multiple');
}
$data = $this->projectZen->buildSelectForm($selectedProgramID, $selectedProgram, $objectType);
/* Finish task #64882.Get the path of the last selected program. */
if(!empty($objectID)) $data['objectPath'] = explode(',', $object->path);
@@ -529,30 +521,26 @@ class project extends control
$this->lang->project->subAclList = $this->lang->project->kanbanSubAclList;
}
$withProgram = $this->config->systemMode == 'ALM';
$linkedBranchList = array();
$productPlans = array();
$withProgram = $this->config->systemMode == 'ALM';
$branches = $this->project->getBranchesByProject($projectID);
$linkedProductIdList = empty($branches) ? '' : array_keys($branches);
$linkedProductIdList = empty($branches) ? array() : array_keys($branches);
$allProducts = $this->program->getProductPairs($project->parent, 'all', 'noclosed', '', 0, $withProgram);
$linkedProducts = $this->loadModel('product')->getProducts($projectID, 'all', '', true, $linkedProductIdList);
$parentProject = $this->program->getByID($project->parent);
$linkedProducts = $this->loadModel('product')->getProducts($projectID, 'all', '', true);
$plans = $this->productplan->getGroupByProduct(array_keys($linkedProducts), 'skipParent|unexpired');
$parentProject = $this->program->getByID($project->parent);
$projectStories = $this->project->getStoriesByProject($projectID);
$projectBranches = $this->project->getBranchGroupByProject($projectID, array_keys($linkedProducts));
if($_POST)
{
$postData = form::data($this->config->project->form->edit);
$project = $this->projectZen->prepareEditExtras($postData);
$project = $this->projectZen->prepareEditExtras($projectID, $postData);
$project->id = $projectID;
$project->products = isset($project->products) ? array_filter($project->products) : $linkedProducts;
$plans = $project->plans;
if(!empty($plans)) $this->project->updatePlanIdListByProject($projectID, $plans);
$changes = $this->project->update($projectID, $project);
$changes = $this->project->update($projectID, $project, $postData);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
if($changes)
+1 -1
View File
@@ -24,5 +24,5 @@
.productsBox > #productNameLabel {padding-top: 8px;}
.futureBox {vertical-align: top !important; padding-top: 13px !important;}
#projectType {padding-top: 7px !important;}
.division .icon-help {margin-left: 15px}
.stageBy .icon-help {margin-left: 15px}
.has-info {border-color: #0c64eb!important;}
+6 -6
View File
@@ -6,7 +6,7 @@ $(function()
$('#productTitle, #linkPlan').closest('tr').toggle(hasProduct == 1);
if(hasProduct == 0) $('.division').addClass('hide');
if(hasProduct == 0) $('.stageBy').addClass('hide');
if(hasProduct == 1)
{
@@ -15,7 +15,7 @@ $(function()
{
if($(this).val() > 0) chosenProducts ++;
});
if(chosenProducts > 1) $('.division').removeClass('hide');
if(chosenProducts > 1) $('.stageBy').removeClass('hide');
}
});
@@ -221,7 +221,7 @@ function addNewProduct(obj)
$('.newLine').addClass('hidden');
$('.productsBox .row').addClass('hidden');
$('.productsBox .row .input-group').find('select').attr('disabled', true).trigger("chosen:updated");
$('.division').addClass('hide');
$('.stageBy').addClass('hide');
/* Displays the input box for creating a product. */
$(obj).closest('td').attr('colspan', 1);
@@ -236,7 +236,7 @@ function addNewProduct(obj)
$('.newLine').removeClass('hidden');
$('.productsBox .row').removeClass('hidden');
$('.productsBox .row .input-group').find('select').removeAttr('disabled').trigger("chosen:updated");
if($('.productsBox').find("select[name^=products]").length > 1) $('.division').removeClass('hide');
if($('.productsBox').find("select[name^=products]").length > 1) $('.stageBy').removeClass('hide');
/* Hide the input box for creating a product. */
$(obj).closest('td').attr('colspan', 3);
@@ -297,7 +297,7 @@ function loadBranches(product)
}
});
(chosenProducts.length > 1 && (model == 'waterfall' || model == 'waterfallplus')) ? $('.division').removeClass('hide') : $('.division').addClass('hide');
(chosenProducts.length > 1 && (model == 'waterfall' || model == 'waterfallplus')) ? $('.stageBy').removeClass('hide') : $('.stageBy').addClass('hide');
var $tableRow = $(product).closest('.table-row');
var index = $tableRow.find('select:first').attr('id').replace('products' , '');
@@ -403,7 +403,7 @@ function removeLine(obj)
{
$(obj).closest('tr').remove();
disableSelectedProduct();
if($("select[name^='products']").length < 2) $('.division').addClass('hide');
if($("select[name^='products']").length < 2) $('.stageBy').addClass('hide');
}
/**
+4 -4
View File
@@ -67,8 +67,8 @@ $(function()
{
if($(this).val() > 0) chosenProducts ++;
});
if(chosenProducts > 1) $('.division').removeClass('hide');
if(chosenProducts <= 1) $('.division').addClass('hide');
if(chosenProducts > 1) $('.stageBy').removeClass('hide');
if(chosenProducts <= 1) $('.stageBy').addClass('hide');
});
$(document).on('change', '[name*=branch]', function()
@@ -205,7 +205,7 @@ function loadBranches(product)
}
});
(chosenProducts.length > 1 && (model == 'waterfall' || model == 'waterfallplus')) ? $('.division').removeClass('hide') : $('.division').addClass('hide');
(chosenProducts.length > 1 && (model == 'waterfall' || model == 'waterfallplus')) ? $('.stageBy').removeClass('hide') : $('.stageBy').addClass('hide');
var $tableRow = $(product).closest('.table-row');
var index = $tableRow.find('select:first').attr('id').replace('products' , '');
@@ -308,5 +308,5 @@ function removeLine(obj)
if($(this).val() > 0) chosenProducts ++;
});
(chosenProducts.length > 1 && (model == 'waterfall' || model == 'waterfallplus')) ? $('.division').removeClass('hide') : $('.division').addClass('hide');
(chosenProducts.length > 1 && (model == 'waterfall' || model == 'waterfallplus')) ? $('.stageBy').removeClass('hide') : $('.stageBy').addClass('hide');
}
+1 -1
View File
@@ -117,7 +117,7 @@ $(function()
}
})
if(project.division && project.hasProduct && $('#executionList thead th.table-nest-title').width() < 240)
if((project.stageBy == 'product') && project.hasProduct && $('#executionList thead th.table-nest-title').width() < 240)
{
$('#executionList thead th.table-nest-title').width(240)
}
+4 -4
View File
@@ -52,7 +52,7 @@ $(function()
if(response.result == 'success')
{
$('#productsBox #submit').popover('destroy');
$('.saveOtherProduct').popover(
{
trigger: 'manual',
@@ -118,17 +118,17 @@ function notice()
'message' : noticeDivsion,
'buttons':{
confirm:{
label: divisionSwitchList['1'],
label: stageBySwitchList['1'],
className: 'btn'
},
cancel:{
label: divisionSwitchList['0'],
label: stageBySwitchList['0'],
className: 'btn-primary'
},
},
callback: function(result)
{
if(result) $('#submit').after("<input type='hidden' value='1' name='division'>");
if(result) $('#submit').after("<input type='hidden' value='1' name='stageBy'>");
$('form#productsBox').submit();
}
}
+6 -6
View File
@@ -138,7 +138,7 @@ $lang->project->realBeganAB = 'Actual Begin';
$lang->project->realEndAB = 'Actual End';
$lang->project->realBegan = 'Actual Begin';
$lang->project->realEnd = 'Actual End';
$lang->project->division = 'Stage Type';
$lang->project->stageBy = 'Stage Type';
$lang->project->bygrid = 'Kanban';
$lang->project->bylist = 'List';
$lang->project->bycard = 'Card';
@@ -194,11 +194,11 @@ $lang->project->typeList = array();
$lang->project->typeList['my'] = "{$lang->projectCommon}s Ownedbyme";
$lang->project->typeList['other'] = "Other {$lang->projectCommon}s";
$lang->project->divisionList['0'] = "Create by {$lang->projectCommon}";
$lang->project->divisionList['1'] = "Create by {$lang->productCommon}";
$lang->project->stageByList['0'] = "Create by {$lang->projectCommon}";
$lang->project->stageByList['1'] = "Create by {$lang->productCommon}";
$lang->project->divisionSwitchList['0'] = 'Close';
$lang->project->divisionSwitchList['1'] = "Open";
$lang->project->stageBySwitchList['0'] = 'Close';
$lang->project->stageBySwitchList['1'] = "Open";
$lang->project->waitProjects = "Waiting {$lang->projectCommon}s";
$lang->project->doingProjects = "Ongoing {$lang->projectCommon}s";
@@ -418,7 +418,7 @@ $lang->project->begigLetterExecution = "The start date of {$lang->projectCommon}
$lang->project->endGreateExecution = "The finish date of the {$lang->projectCommon} should be ≥ the maximum finish date of the execution: %s.";
$lang->project->childLongTime = "There are long-term {$lang->projectCommon}s in the child {$lang->projectCommon}, and the parent {$lang->projectCommon} should also be a long-term {$lang->projectCommon}.";
$lang->project->confirmUnlinkMember = "Do you want to remove this user from {$lang->projectCommon}?";
$lang->project->divisionTips = "Created by {$lang->projectCommon} as a single set of stages, and the stage is associated with all {$lang->productCommon}s; created by {$lang->productCommon} as multiple sets of stages, each set of stages is associated with a {$lang->productCommon}";
$lang->project->stageByTips = "Created by {$lang->projectCommon} as a single set of stages, and the stage is associated with all {$lang->productCommon}s; created by {$lang->productCommon} as multiple sets of stages, each set of stages is associated with a {$lang->productCommon}";
$lang->project->action = new stdclass();
$lang->project->action->managed = '$date, managed by <strong>$actor</strong>. $extra' . "\n";
+6 -6
View File
@@ -138,7 +138,7 @@ $lang->project->realBeganAB = 'Actual Begin';
$lang->project->realEndAB = 'Actual End';
$lang->project->realBegan = 'Actual Begin';
$lang->project->realEnd = 'Actual End';
$lang->project->division = 'Stage Type';
$lang->project->stageBy = 'Stage Type';
$lang->project->bygrid = 'Kanban';
$lang->project->bylist = 'List';
$lang->project->bycard = 'Card';
@@ -196,11 +196,11 @@ $lang->project->typeList = array();
$lang->project->typeList['my'] = "{$lang->projectCommon}s Ownedbyme";
$lang->project->typeList['other'] = "Other {$lang->projectCommon}s";
$lang->project->divisionList['0'] = "Create by {$lang->projectCommon}";
$lang->project->divisionList['1'] = "Create by {$lang->productCommon}";
$lang->project->stageBy == 'project'List['0'] = "Create by {$lang->projectCommon}";
$lang->project->stageBy == 'project'List['1'] = "Create by {$lang->productCommon}";
$lang->project->divisionSwitchList['0'] = 'Close';
$lang->project->divisionSwitchList['1'] = "Open";
$lang->project->stageBy == 'project'SwitchList['0'] = 'Close';
$lang->project->stageBy == 'project'SwitchList['1'] = "Open";
$lang->project->waitProjects = "Waiting {$lang->projectCommon}s";
$lang->project->doingProjects = "Ongoing {$lang->projectCommon}s";
@@ -420,7 +420,7 @@ $lang->project->begigLetterExecution = "The start date of {$lang->projectCommon}
$lang->project->endGreateExecution = "The finish date of the {$lang->projectCommon} should be ≥ the maximum finish date of the execution: %s.";
$lang->project->childLongTime = "There are long-term {$lang->projectCommon}s in the child {$lang->projectCommon}, and the parent {$lang->projectCommon} should also be a long-term {$lang->projectCommon}.";
$lang->project->confirmUnlinkMember = "Do you want to remove this user from {$lang->projectCommon}?";
$lang->project->divisionTips = "Created by {$lang->projectCommon} as a single set of stages, and the stage is associated with all {$lang->productCommon}s; created by {$lang->productCommon} as multiple sets of stages, each set of stages is associated with a {$lang->productCommon}";
$lang->project->stageByTips = "Created by {$lang->projectCommon} as a single set of stages, and the stage is associated with all {$lang->productCommon}s; created by {$lang->productCommon} as multiple sets of stages, each set of stages is associated with a {$lang->productCommon}";
$lang->project->action = new stdclass();
$lang->project->action->managed = '$date, managed by <strong>$actor</strong>. $extra' . "\n";
+6 -6
View File
@@ -139,7 +139,7 @@ $lang->project->realBeganAB = 'Actual Begin';
$lang->project->realEndAB = 'Actual End';
$lang->project->realBegan = 'Actual Begin';
$lang->project->realEnd = 'Actual End';
$lang->project->division = 'Stage Type';
$lang->project->stageBy = 'Stage Type';
$lang->project->bygrid = 'Kanban';
$lang->project->bylist = 'List';
$lang->project->bycard = 'Card';
@@ -195,11 +195,11 @@ $lang->project->typeList = array();
$lang->project->typeList['my'] = "{$lang->projectCommon}s Ownedbyme";
$lang->project->typeList['other'] = "Other {$lang->projectCommon}s";
$lang->project->divisionList['0'] = "Create by {$lang->projectCommon}";
$lang->project->divisionList['1'] = "Create by {$lang->productCommon}";
$lang->project->stageByList['0'] = "Create by {$lang->projectCommon}";
$lang->project->stageByList['1'] = "Create by {$lang->productCommon}";
$lang->project->divisionSwitchList['0'] = 'Close';
$lang->project->divisionSwitchList['1'] = "Open";
$lang->project->stageBySwitchList['0'] = 'Close';
$lang->project->stageBySwitchList['1'] = "Open";
$lang->project->waitProjects = "Waiting {$lang->projectCommon}s";
$lang->project->doingProjects = "Ongoing {$lang->projectCommon}s";
@@ -418,7 +418,7 @@ $lang->project->begigLetterExecution = "The start date of {$lang->projectCommon}
$lang->project->endGreateExecution = "The finish date of the {$lang->projectCommon} should be ≥ the maximum finish date of the execution: %s.";
$lang->project->childLongTime = "There are long-term {$lang->projectCommon}s in the child {$lang->projectCommon}, and the parent {$lang->projectCommon} should also be a long-term {$lang->projectCommon}.";
$lang->project->confirmUnlinkMember = "Do you want to remove this user from {$lang->projectCommon}?";
$lang->project->divisionTips = "Created by {$lang->projectCommon} as a single set of stages, and the stage is associated with all {$lang->productCommon}s; created by {$lang->productCommon} as multiple sets of stages, each set of stages is associated with a {$lang->productCommon}";
$lang->project->stageByTips = "Created by {$lang->projectCommon} as a single set of stages, and the stage is associated with all {$lang->productCommon}s; created by {$lang->productCommon} as multiple sets of stages, each set of stages is associated with a {$lang->productCommon}";
$lang->project->action = new stdclass();
$lang->project->action->managed = '$date, managed by <strong>$actor</strong>. $extra' . "\n";
+4 -4
View File
@@ -134,7 +134,7 @@ $lang->project->realBeganAB = 'Actual Begin';
$lang->project->realEndAB = 'Actual End';
$lang->project->realBegan = 'Actual Begin';
$lang->project->realEnd = 'Actual End';
$lang->project->division = 'Stage Type';
$lang->project->stageBy = 'Stage Type';
$lang->project->bygrid = 'Kanban';
$lang->project->bylist = 'List';
$lang->project->bycard = 'Card';
@@ -190,8 +190,8 @@ $lang->project->typeList = array();
$lang->project->typeList['my'] = 'Projects Ownedbyme';
$lang->project->typeList['other'] = 'Other Projects';
$lang->project->divisionList['1'] = 'Create by project';
$lang->project->divisionList['0'] = 'Create by product';
$lang->project->stageByList['1'] = 'Create by project';
$lang->project->stageByList['0'] = 'Create by product';
$lang->project->waitProjects = 'Waiting Projects';
$lang->project->doingProjects = 'Ongoing Projects';
@@ -366,7 +366,7 @@ $lang->project->begigLetterExecution = 'The start date of project should be ≤
$lang->project->endGreateExecution = 'The finish date of the project should be ≥ the maximum finish date of the execution: %s.';
$lang->project->childLongTime = "There are long-term projects in the child project, and the parent project should also be a long-term project.";
$lang->project->confirmUnlinkMember = "Do you want to remove this user from project?";
$lang->project->divisionTips = "Created by project as a single set of stages, and the stage is associated with all products; created by product as multiple sets of stages, each set of stages is associated with a product";
$lang->project->stageByTips = "Created by project as a single set of stages, and the stage is associated with all products; created by product as multiple sets of stages, each set of stages is associated with a product";
$lang->project->action = new stdclass();
$lang->project->action->managed = '$date, managed by <strong>$actor</strong>. $extra' . "\n";
+6 -6
View File
@@ -138,7 +138,7 @@ $lang->project->realBeganAB = '实际开始';
$lang->project->realEndAB = '实际完成';
$lang->project->realBegan = '实际开始日期';
$lang->project->realEnd = '实际完成日期';
$lang->project->division = '阶段类型';
$lang->project->stageBy = '阶段类型';
$lang->project->bygrid = '看板';
$lang->project->bylist = '列表';
$lang->project->bycard = '卡片';
@@ -195,11 +195,11 @@ $lang->project->typeList = array();
$lang->project->typeList['my'] = "我负责的{$lang->projectCommon}";
$lang->project->typeList['other'] = "其他{$lang->projectCommon}";
$lang->project->divisionList['0'] = "按{$lang->projectCommon}创建";
$lang->project->divisionList['1'] = "按{$lang->productCommon}创建";
$lang->project->stageByList['0'] = "按{$lang->projectCommon}创建";
$lang->project->stageByList['1'] = "按{$lang->productCommon}创建";
$lang->project->divisionSwitchList['0'] = '关闭';
$lang->project->divisionSwitchList['1'] = "开启";
$lang->project->stageBySwitchList['0'] = '关闭';
$lang->project->stageBySwitchList['1'] = "开启";
$lang->project->waitProjects = "未开始的{$lang->projectCommon}";
$lang->project->doingProjects = "进行中的{$lang->projectCommon}";
@@ -419,7 +419,7 @@ $lang->project->begigLetterExecution = "{$lang->projectCommon}的开始日期应
$lang->project->endGreateExecution = "{$lang->projectCommon}的完成日期应大于等于执行的最大完成日期:%s";
$lang->project->childLongTime = "子{$lang->projectCommon}中有长期{$lang->projectCommon},父{$lang->projectCommon}也应该是长期{$lang->projectCommon}";
$lang->project->confirmUnlinkMember = "您确定从该{$lang->projectCommon}中移除该用户吗?";
$lang->project->divisionTips = "按{$lang->projectCommon}创建为单套阶段,阶段关联所有{$lang->productCommon};按{$lang->productCommon}创建为多套阶段,每套阶段关联一个{$lang->productCommon}。";
$lang->project->stageByTips = "按{$lang->projectCommon}创建为单套阶段,阶段关联所有{$lang->productCommon};按{$lang->productCommon}创建为多套阶段,每套阶段关联一个{$lang->productCommon}。";
$lang->project->action = new stdclass();
$lang->project->action->managed = '$date, 由 <strong>$actor</strong> 维护。$extra' . "\n";
+268 -142
View File
@@ -52,20 +52,6 @@ class projectModel extends model
return $this->app->user->admin || str_contains(",{$this->app->user->view->projects},", ",{$projectID},");
}
/**
* Show accessDenied response.
*
* @access private
* @return void
*/
public function accessDenied()
{
if(defined('TUTORIAL')) return true;
$this->session->set('project', '');
return print(js::alert($this->lang->project->accessDenied) . js::locate(helper::createLink('project', 'index')));
}
/**
* Judge an action is clickable or not.
*
@@ -123,7 +109,7 @@ class projectModel extends model
}
$this->session->set('project', (int)key($projects), $this->app->tab);
$this->accessDenied();
print($this->projectTao->accessDenied());
}
else
{
@@ -1226,7 +1212,10 @@ class projectModel extends model
/* If $_POST has product name, create it. */
$linkedProductsCount = $this->projectTao->getLinkedProductsCount($project, $postData->rawdata);
if(!$project->hasProduct or isset($postData->rawdata->newProduct) or (!$project->parent and empty($linkedProductsCount))) $this->createProduct($projectID, $project, $postData, $program);
if(!$project->hasProduct or isset($postData->rawdata->newProduct) or (!$project->parent and empty($linkedProductsCount)))
{
if(!$this->createProduct($projectID, $project, $postData, $program)) return false;
}
/* Save order. */
$this->dao->update(TABLE_PROJECT)->set('`order`')->eq($projectID * 5)->where('id')->eq($projectID)->exec();
@@ -1244,6 +1233,7 @@ class projectModel extends model
}
/**
* 检查输入的$project 变量的branch和product是否合法。
* Check branch and product valid by project.
*
* @param int $projectID
@@ -1267,7 +1257,8 @@ class projectModel extends model
}
/**
* Check dates valid by project.
* 检查执行的起止日期是否小于项目的起止日期。
* Check if execution's start and end dates below project's start and end dates.
*
* @param int $projectID
* @param object $project
@@ -1276,157 +1267,250 @@ class projectModel extends model
*/
public function checkDatesValidByProject($projectID, $project): bool
{
$minExecutionBegin = $this->dao->select('`begin` as minBegin')->from(TABLE_PROJECT)->where('project')->eq($projectID)->andWhere('deleted')->eq('0')->orderBy('begin_asc')->fetch();
$maxExecutionEnd = $this->dao->select('`end` as maxEnd')->from(TABLE_PROJECT)->where('project')->eq($projectID)->andWhere('deleted')->eq('0')->orderBy('end_desc')->fetch();
if($minExecutionBegin and $project->begin > $minExecutionBegin->minBegin) dao::$errors['begin'] = sprintf($this->lang->project->begigLetterExecution, $minExecutionBegin->minBegin);
if($maxExecutionEnd and $project->end < $maxExecutionEnd->maxEnd) dao::$errors['end'] = sprintf($this->lang->project->endGreateExecution, $maxExecutionEnd->maxEnd);
$executionsCount = $this->dao->select('COUNT(*) as count')->from(TABLE_PROJECT)
->where('project') ->eq($projectID)
->andWhere('deleted') ->eq('0')
->fetch('count');
if(empty($executionsCount))return true;
$minExecutionBegin = $this->dao->select('`begin` as minBegin')->from(TABLE_PROJECT)
->where('project')->eq($projectID)
->andWhere('deleted')->eq('0')
->orderBy('begin_asc')
->fetch();
$maxExecutionEnd = $this->dao->select('`end` as maxEnd')->from(TABLE_PROJECT)
->where('project')->eq($projectID)
->andWhere('deleted')->eq('0')
->orderBy('end_desc')
->fetch();
if($minExecutionBegin && ($project->begin > $minExecutionBegin->minBegin)) dao::$errors['begin'] = sprintf($this->lang->project->begigLetterExecution, $minExecutionBegin->minBegin);
if($maxExecutionEnd && ($project->end < $maxExecutionEnd->maxEnd)) dao::$errors['end'] = sprintf($this->lang->project->endGreateExecution, $maxExecutionEnd->maxEnd);
if(dao::isError()) return false;
return true;
}
/**
* Update project.
* 更新项目关联的团队成员列表。
* update teammembers while update project.
*
* @param int $projectID
* @access public
* @return array
* @param int $projectid
* @param object $project
* @param object $oldproject
*/
public function update($projectID, $project)
public function updateTeamMembersByProject(int $projectID, object $project, object $oldProject): bool
{
$oldProject = $this->dao->findById($projectID)->from(TABLE_PROJECT)->fetch();
$linkedProducts = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->fetchPairs();
if(!isset($project->parent)) $project->parent = $oldProject->parent;
$executionsCount = $this->dao->select('COUNT(*) as count')->from(TABLE_PROJECT)->where('project')->eq($project->id)->andWhere('deleted')->eq('0')->fetch('count');
if($project->product && !$this->checkBranchAndProductValid($projectID, $project)) return false;
/* Judge products not empty. */
$linkedProductsCount = 0;
foreach($_POST['products'] as $product)
{
if(!empty($product)) $linkedProductsCount++;
}
if(empty($linkedProductsCount))
{
dao::$errors[] = $this->lang->project->errorNoProducts;
return false;
}
/* Judge workdays is legitimate. */
$workdays = helper::diffDate($project->end, $project->begin) + 1;
if(isset($project->days) and $project->days > $workdays)
{
dao::$errors['days'] = sprintf($this->lang->project->workdaysExceed, $workdays);
return false;
}
$project = $this->loadModel('file')->processImgURL($project, $this->config->project->editor->edit['id'], $this->post->uid);
$requiredFields = $this->config->project->edit->requiredFields;
if($this->post->delta == 999) $requiredFields = trim(str_replace(',end,', ',', ",{$requiredFields},"), ',');
$this->lang->error->unique = $this->lang->error->repeat;
if(!empty($executionsCount) and $oldProject->multiple) $this->checkDatesValidByProject($projectID ,$project);
$this->projectTao->doUpdate($projectID, $project, $oldProject);
if(dao::isError()) return false;
if(!$oldProject->hasProduct and ($oldProject->name != $project->name or $oldProject->parent != $project->parent or $oldProject->acl != $project->acl)) $this->updateShadowProduct($project);
/* Get team and language item. */
if($project->model != 'kanban') return true;
/* Get oldProject's team and roles.*/
$this->loadModel('user');
$team = $this->user->getTeamMemberPairs($projectID, 'project');
$members = isset($_POST['teamMembers']) ? $_POST['teamMembers'] : array();
$members = $project->teamMembers;
array_push($members, $project->PM);
$members = array_unique($members);
$roles = $this->user->getUserRoles(array_values($members));
/* Init members for update members in TEABLE_TEAM */
$member = new stdclass();
$member->root = $projectID;
$member->join = helper::today();
$member->days = zget($project, 'days', 0);
$member->type = 'project';
$member->hours = $this->config->execution->defaultWorkhours;
/* Prepare $teamMembers for addProjectMembers.*/
$teamMembers = array();
foreach($members as $account)
{
if(empty($account) or isset($team[$account])) continue;
$member = new stdclass();
$member->root = (int)$projectID;
$member->account = $account;
$member->join = helper::today();
$member->role = zget($roles, $account, '');
$member->days = zget($project, 'days', 0);
$member->type = 'project';
$member->hours = $this->config->execution->defaultWorkhours;
$this->dao->replace(TABLE_TEAM)->data($member)->exec();
$teamMembers[$account] = $member;
}
/* Delete members while update members in project, $team only exist while oldproject's model is kanban.*/
if($oldProject->model == 'kanban')
{
$this->dao->delete()->from(TABLE_TEAM)
->where('root')->eq((int)$projectID)
->andWhere('type')->eq('project')
->andWhere('account')->in(array_keys($team))
->andWhere('account')->notin(array_values($members))
->andWhere('account')->ne($oldProject->openedBy)
->exec();
$deleteMembers = array_diff(array_keys($team), array_values($members));
$this->projectTao->deleteExtraMembersByProject($projectID, $oldProject->openedBy, $deleteMembers);
}
/* Add projectMembers by vars members.*/
if(!empty($projectID) and !empty($teamMembers)) $this->loadModel('execution')->addProjectMembers($projectID, $teamMembers);
if(!dao::isError())
return !dao::isError();
}
/**
* 更新此项目下的白名单列表。
* Update whitelist by project.
*
* @param int $projectID
* @param object $project
* @param object $oldProject
* @access public
* @return bool
*/
public function updateWhitelistByProject(int $projectID, object $project, object $oldProject, array $linkedProducts): bool
{
/* Check if whitelist shoud update .*/
$whitelist = array_filter(explode(',', $project->whitelist));
$oldWhitelist = explode(',', $oldProject->whitelist) and $oldWhitelist = array_filter($oldWhitelist);
if(count($oldWhitelist) == count($whitelist) and !empty(array_diff($oldWhitelist, $whitelist)))
{
$this->updateProducts($projectID, $_POST['products']);
if(empty($oldProject->division))
{
$executions = $this->loadModel('execution')->getPairs($projectID);
foreach(array_keys($executions) as $executionID) $this->execution->updateProducts($executionID);
}
$this->file->updateObjectID($this->post->uid, $projectID, 'project');
$whitelist = explode(',', $project->whitelist);
$this->loadModel('personnel')->updateWhitelist($whitelist, 'project', $projectID);
if(!$oldProject->hasProduct) $this->loadModel('personnel')->updateWhitelist($whitelist, 'product', current($linkedProducts));
if($project->acl != 'open')
{
$this->loadModel('user')->updateUserView($projectID, 'project');
$executions = $this->dao->select('id')->from(TABLE_EXECUTION)->where('project')->eq($projectID)->fetchPairs('id', 'id');
if($executions) $this->user->updateUserView($executions, 'sprint');
}
if($oldProject->parent != $project->parent) $this->loadModel('program')->processNode($projectID, $project->parent, $oldProject->path, $oldProject->grade);
/* Fix whitelist changes. */
$oldWhitelist = explode(',', $oldProject->whitelist) and $oldWhitelist = array_filter($oldWhitelist);
$newWhitelist = explode(',', $project->whitelist) and $newWhitelist = array_filter($newWhitelist);
if(count($oldWhitelist) == count($newWhitelist) and count(array_diff($oldWhitelist, $newWhitelist)) == 0) unset($project->whitelist);
/* Add linkedproducts changes. */
$oldProject->linkedProducts = implode(',', $linkedProducts);
$project->linkedProducts = implode(',', $_POST['products']);
$unlinkedProducts = array_diff($linkedProducts, $_POST['products']);
if(!empty($unlinkedProducts))
{
$products = $this->dao->select('name')->from(TABLE_PRODUCT)->where('id')->in($unlinkedProducts)->fetchPairs();
$this->loadModel('action')->create('project', $projectID, 'unlinkproduct', '', implode(',', $products));
}
/* Activate or close the shadow product of the project. */
if(!$oldProject->hasProduct && $oldProject->status != $project->status && strpos('doing,closed', $project->status) !== false)
{
$productID = $this->loadModel('product')->getProductIDByProject($projectID);
if($project->status == 'doing') $this->product->activate($productID);
if($project->status == 'closed') $this->product->close($productID);
}
if(empty($oldProject->multiple) and $oldProject->model != 'waterfall') $this->loadModel('execution')->syncNoMultipleSprint($projectID);
return common::createChanges($oldProject, $project);
}
return !dao::isError();
}
/**
* 解除部分关联产品并且记录动作。
* Unlink products by project.
*
* @param int $projectID
* @param object $project
* @param object $oldProject
* @access public
* @return bool
*/
public function unLinkProductsByProject(int $projectID, object $project, object $oldProject): bool
{
$linkedProducts = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)
->where('project')->eq($projectID)
->fetchPairs();
$oldProject->linkedProducts = implode(',', $linkedProducts);
$project->linkedProducts = implode(',', $project->products);
$unlinkedProducts = array_diff($linkedProducts, $project->products);
if(empty($unlinkedProducts)) return true;
$products = $this->dao->select('name')->from(TABLE_PRODUCT)
->where('id')->in($unlinkedProducts)
->fetchPairs();
$this->loadModel('action')->create('project', $projectID, 'unlinkproduct', '', implode(',', $products));
return !dao::isError();
}
/**
* 更新项目的影子产品的状态。
* Update shadowProducts' status by Project.
*
* @param int $projectID
* @param string $status
* @access public
* @return bool
*/
public function updateShadowProductStatus(int $projectID, string $status): bool
{
/* Activate or close the shadow product of the project. */
$productID = $this->loadModel('product')->getProductIDByProject($projectID);
if($status == 'doing') $this->product->activate($productID);
if($status == 'closed') $this->product->close($productID);
return !dao::isError();
}
/**
* 更新项目关联的用户视图。
* Update user view by project.
*
* @param int $projectID
* @param string $acl
* @access public
* @return bool
*/
public function updateUserViewByProject(int $projectID, string $acl): bool
{
if($acl == 'open') return true;
$this->loadModel('user')->updateUserView($projectID, 'project');
$executions = $this->dao->select('id')->from(TABLE_EXECUTION)
->where('project')->eq($projectID)
->fetchPairs();
if($executions) $this->user->updateUserView($executions, 'sprint');
return !dao::isError();
}
/**
* 更新项目下的所有产品的阶段。
* Update product stage by project.
*
* @param int $projectID
* @param strubg $stageBy
* @access public
* @return bool
*/
public function stageProduct(int $projectID, string $stageBy): bool
{
/* 如果项目的阶段为由按项目创建,则更新此项目下每个产品。*/
if($stageBy == 'product') return true;
$executions = $this->loadModel('execution')->getPairs($projectID);
foreach(array_keys($executions) as $executionID) $this->execution->updateProducts($executionID); // 更新项目下所有产品的阶段 this->project->stageProducts();
return dao::isError();
}
/**
* 更新项目。
* Update project.
*
* @param int $projectID
* @param object $project
* @access public
* @return array|false
*/
public function update(int $projectID, object $project , object $postData): array|false
{
/* 通过id查老项目信息, 处理parent和图片字段。*/
/* Fetch oldProject's info and get parent and file info.*/
$oldProject = $this->projectTao->fetchProjectInfo($projectID);
if(!isset($project->parent)) $project->parent = $oldProject->parent;
$project = $this->loadModel('file')->processImgURL($project, $this->config->project->editor->edit['id'], $this->post->uid);
/* 若此项目为多迭代项目, 检查起止日期不得小于迭代的起止日期。*/
/* If this project has multiStage, check if execution's start and end date below project's start and end date.*/
if($oldProject->multiple and !$this->checkDatesValidByProject($projectID, $oldProject)) return false;
/* 更新project表。*/
/* Update project table.*/
$result = $this->projectTao->doUpdate($projectID, $project, $oldProject);
if(!$result) return false;
/* 如果是无产品项目,更新影子产品信息。*/
/* If this is a project without product, update shadow product's info by project.*/
if(!$oldProject->hasProduct)
{
/* If oldProject has no product and name or parent or acl has changed, update shadow product.*/
if($oldProject->name != $project->name || $oldProject->parent != $project->parent or $oldProject->acl != $project->acl) $this->updateShadowProduct($project);
/* If oldProject has no product and status has changed to dong or closed updateShadowProductStatus.*/
if($oldProject->status != $project->status && str_contains('doing,closed', $project->status)) $this->updateShadowProductStatus($projectID, $project->status);
}
/* 更新项目的关联信息。*/
/* Update relation info of this project.*/
$this->stageProduct($projectID, $oldProject->stageBy); // 更新关联的所有产品的阶段。
$this->updateProducts($projectID, $project->product); // 更新关联的项目列表。
$this->unLinkProductsByProject($projectID, $project, $oldProject); // 解除关联部分关联的产品信息。
$this->updateUserViewByProject($projectID, $project->acl); // 更新用户视图。
$this->updateWhitelistByProject($projectID, $project, $oldProject, $linkedProducts); // 更新关联的白名单列表。
$this->updateTeamMembersByProject($projectID, $project, $oldProject); // 更新关联的成员列表。
$this->updatePlanIdListByProject($projectID, $project->plans); // 更新关联的计划列表。
$this->file->updateObjectID($this->post->uid, $projectID, 'project'); // 通过uid更新文件id。
if($oldProject->parent != $project->parent) $this->loadModel('program')->fixPath($projectID, $project->parent, $oldProject->path, $oldProject->grade); // 更新项目从属路径。
if(empty($oldProject->multiple) and $oldProject->model != 'waterfall') $this->loadModel('execution')->syncNoMultipleSprint($projectID); // 无迭代的非瀑布项目需要更新。
if(dao::isError()) return false;
return common::createChanges($oldProject, $project);
}
/**
@@ -2178,10 +2262,10 @@ class projectModel extends model
}
$this->user->updateUserView($products, 'product', $members);
if((int)$projectID > 0 and !empty($_POST['division']))
if((int)$projectID > 0 and !empty($_POST['stageBy']))
{
$this->dao->update(TABLE_PROJECT)->set('division')->eq('1')->where('id')->eq((int)$projectID)->exec();
$this->dao->update(TABLE_EXECUTION)->set('division')->eq('1')->where('project')->eq((int)$projectID)->exec();
$this->dao->update(TABLE_PROJECT)->set('stageBy')->eq('product')->where('id')->eq((int)$projectID)->exec();
$this->dao->update(TABLE_EXECUTION)->set('stageBy')->eq('product')->where('project')->eq((int)$projectID)->exec();
}
return !dao::isError();
@@ -2244,14 +2328,14 @@ class projectModel extends model
$executions = $this->dao->select('id')->from(TABLE_EXECUTION)->where('project')->eq($projectID)->fetchPairs('id');
$this->dao->delete()->from(TABLE_PROJECTPRODUCT)->where('project')->in($executions)->andWhere('product')->notin($products)->exec();
if(!empty($_POST['division']))
if(!empty($_POST['stageBy']))
{
$this->dao->update(TABLE_PROJECT)->set('division')->eq('1')->where('id')->eq((int)$projectID)->exec();
$this->dao->update(TABLE_EXECUTION)->set('division')->eq('1')->where('project')->eq((int)$projectID)->exec();
$this->dao->update(TABLE_PROJECT)->set('stageBy')->eq('product')->where('id')->eq((int)$projectID)->exec();
$this->dao->update(TABLE_EXECUTION)->set('stageBy')->eq('product')->where('project')->eq((int)$projectID)->exec();
}
$project = $this->projectTao->fetchProjectInfo($projectID);
if(!empty($project) and ($project->model == 'waterfall' or $project->model == 'waterfallplus') and empty($project->division) and !empty($executions))
if(!empty($project) and ($project->model == 'waterfall' or $project->model == 'waterfallplus') and ($project->stageBy == 'project') and !empty($executions))
{
$this->loadModel('execution');
foreach($executions as $executionID) $this->execution->updateProducts($executionID);
@@ -2464,6 +2548,7 @@ class projectModel extends model
{
global $lang;
$objectID = (int)$objectID;
$model = 'scrum';
$objectID = (empty($objectID) and $this->session->project) ? $this->session->project : $objectID;
$project = $this->projectTao->fetchProjectInfo($objectID);
@@ -2923,7 +3008,7 @@ class projectModel extends model
}
/**
* Fetch planIdList by project
* Update planIdList by project
*
* @param int $projectID
* @param array $plans
@@ -2996,4 +3081,45 @@ class projectModel extends model
return !dao::isError();
}
/**
* 获取项目集的最小开始时间
* Get program min begin
*
* @param int $objectID
*
* @access public
* @return object
*/
public function getProgramMinBegin(int $objectID): object
{
$a = $this->dao->select('`begin` as minBegin')->from(TABLE_PROGRAM)
->where('id')->ne($objectID)
->andWhere('deleted')->eq(0)
->andWhere('path')->like("%,{$objectID},%")
->orderBy('begin_asc')
->fetch('minBegin');
return $a;
}
/**
* 获取项目集的最大结束时间
* get program max end
*
* @param int $objectID
*
* @access public
* @return object
*/
public function getProgramMaxEnd(int $objectID): object
{
return $this->dao->select('`end` as maxEnd')->from(TABLE_PROGRAM)
->where('id')->ne($objectID)
->andWhere('deleted')->eq(0)
->andWhere('path')->like("%,{$objectID},%")
->andWhere('end')->ne('0000-00-00')
->orderBy('end_desc')
->fetch('maxEnd');
}
}
+25 -5
View File
@@ -95,7 +95,6 @@ class projectTao extends projectModel
return !dao::isError();
}
/**
* Update project table when edit a project.
*
@@ -127,9 +126,9 @@ class projectTao extends projectModel
*
* @param int $projectID
* @access protected
* @return array|false
* @return array
*/
protected function fetchUndoneTasks(int $projectID): array|false
protected function fetchUndoneTasks(int $projectID): array
{
return $this->dao->select('id,estStarted,deadline,status')->from(TABLE_TASK)
->where('deadline')->notZeroDate()
@@ -215,6 +214,8 @@ class projectTao extends projectModel
*/
protected function setProjectTeam(int $projectID, object $project, object $postData): array
{
$this->loadModel('execution');
/* Set team of project. */
$members = isset($postData->rawdata->teamMembers) ? $postData->rawdata->teamMembers : array();
array_push($members, $project->PM, $project->openedBy);
@@ -304,7 +305,7 @@ class projectTao extends projectModel
{
/* If parent not empty, link products or create products. */
$product = new stdclass();
$product->name = $project->hasProduct && $postData->rawdata->productName ? $postData->rawdata->productName : $project->name;
$product->name = $project->hasProduct && !empty($postData->rawdata->productName) ? $postData->rawdata->productName : zget($project, 'name', '');
$product->shadow = zget($project, 'vision', 'rnd') == 'rnd' ? (int)empty($project->hasProduct) : 1;
$product->bind = $postData->rawdata->parent ? 0 : 1;
$product->program = $project->parent ? current(array_filter(explode(',', $program->path))) : 0;
@@ -321,7 +322,13 @@ class projectTao extends projectModel
$product->createdVersion = $this->config->version;
$product->vision = zget($project, 'vision', 'rnd');
$this->dao->insert(TABLE_PRODUCT)->data($product)->exec();
$this->app->loadConfig('product');
$this->dao->insert(TABLE_PRODUCT)->data($product)
->batchCheck($this->config->product->create->requiredFields, 'notempty')
->checkIF(!empty($product->name), 'name', 'unique', "`program` = {$product->program} and `deleted` = '0'")
->exec();
if(dao::isError()) return false;
$productID = $this->dao->lastInsertId();
if(!$project->hasProduct) $this->loadModel('personnel')->updateWhitelist($whitelist, 'product', $productID);
$this->loadModel('action')->create('product', $productID, 'opened');
@@ -787,6 +794,19 @@ class projectTao extends projectModel
->fetchAll('project');
}
/**
* 返回请求被拒绝的跳转信息。
* return accessDenied response.
*
* @access protected
* @return string
*/
protected function accessDenied(): string
{
$this->session->set('project', '');
return js::alert($this->lang->project->accessDenied) . js::locate(helper::createLink('project', 'index'));
}
/**
* 通过项目ID获取Bug数量统计。
* Get the number of bugs associated with the project.
@@ -17,7 +17,7 @@ function initData()
$projectproduct = zdTable('projectproduct');
$projectproduct->product->range('1-3');
$projectproduct->project->range('2-4');
$projectproduct->branch->range("0");
$projectproduct->branch->range("0{5}");
$projectproduct->gen(3);
}
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
su('admin');
$project = zdTable('project')->config('project')->gen(6);
/**
title=测试 projectModel->getProgramMaxEnd();
cid=1
pid=1
*/
global $tester;
$tester->loadModel('project');
$project1 = $tester->project->getProgramMaxEnd(1);
$project2 = $tester->project->getProgramMaxEnd(3);
$project3 = $tester->project->getProgramMaxEnd(6);
a($project1);die;
r($project1) && p() && e(); //获取一级项目集最小开始时间
r($project1) && p() && e(); //获取一级项目集最大结束时间
r($project2) && p() && e(); //获取二级项目集最小开始时间
r($project2) && p() && e(); //获取二级项目集最大结束时间
r($project3) && p() && e(); //获取三级项目集最小开始时间
r($project3) && p() && e(); //获取三级项目集最大结束时间
+14 -2
View File
@@ -4,7 +4,7 @@ include dirname(__FILE__, 5) . "/test/lib/init.php";
su('admin');
$project = zdTable('project')->config('1')->gen(8);
$project = zdTable('project')->config('project')->gen(8);
/**
@@ -12,6 +12,18 @@ title=测试 projectTao::getstats4kanban
timeout=0
cid=1
PHP Warning: Undefined array key 1 in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Deprecated: substr(): Passing null to parameter #1 ($string) of type string is deprecated in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Warning: Undefined array key 1 in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Deprecated: substr(): Passing null to parameter #1 ($string) of type string is deprecated in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Warning: Undefined array key 1 in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Deprecated: substr(): Passing null to parameter #1 ($string) of type string is deprecated in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Warning: Undefined array key 1 in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Deprecated: substr(): Passing null to parameter #1 ($string) of type string is deprecated in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Warning: Undefined array key 1 in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Deprecated: substr(): Passing null to parameter #1 ($string) of type string is deprecated in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Warning: Undefined array key 1 in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Deprecated: substr(): Passing null to parameter #1 ($string) of type string is deprecated in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
- 执行$kanbanGroup['kanbanGroup'] @2
- 执行$kanbanGroup['kanbanGroup']['other'] @3
@@ -47,4 +59,4 @@ r($kanbanGroup['kanbanGroup']['other'][4]['doing'][0]->status) && p() && e('doin
r($kanbanGroup['kanbanGroup']['my'][1]['wait'][0]->status) && p() && e('wait');
r($kanbanGroup['kanbanGroup']['my'][3]['doing'][0]->status) && p() && e('doing');
r($kanbanGroup['kanbanGroup']['my'][5]['doing'][0]->status) && p() && e('doing');
r($kanbanGroup['latestExecutions'][6]->status) && p() && e('doing');
r($kanbanGroup['latestExecutions'][6]->status) && p() && e('doing');
@@ -0,0 +1,21 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/project.class.php';
su('admin');
/**
title=测试 projectModel::stageProduct();
cid=1
pid=1
*/
global $tester;
$tester->app->loadConfig('execution');
$project = new Project();
r($project->stageProduct(1, 'product')) && p('') && e(1); // 更新项目下的所有产品的阶段。
r($project->stageProduct(2, 'project')) && p('') && e(1); // 更新项目下的所有产品的阶段。
@@ -0,0 +1,23 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/project.class.php';
su('admin');
/**
title=测试 projectModel::unLinkProductsByProject();
cid=1
pid=1
*/
global $tester;
$tester->app->loadConfig('execution');
$project = new Project();
$oldProject = new stdclass();
$data = new stdclass();
r($project->stageProduct(1, $data, $oldProject)) && p('') && e(1); // 解除关联部分关联的产品信息。
r($project->stageProduct(2, $data, $oldProject)) && p('') && e(1); // 解除关联部分关联的产品信息。
+13 -6
View File
@@ -54,9 +54,16 @@ $emptyEndProject['end'] = '';
$beginGtExecutionBegin = $data;
$beginGtExecutionBegin['begin'] = '2022-08-06';
r($project->update(20, $normalProject)) && p('name') && e('测试更新项目十'); // 正常更新项目的情况
r($project->update(20, $noProductProject)) && p('message:0') && e('最少关联一个产品'); // 未关联产品的项目
r($project->update(20, $emptyTitleProject)) && p('message[name]:0') && e('『项目名称』不能为空。'); // 更新项目名称为空时
r($project->update(20, $emptyBeginProject)) && p('message[begin]:0') && e('『计划开始』不能为空。'); // 当计划完成为空时更新项目信息
r($project->update(20, $emptyEndProject)) && p('message:end') && e('项目的完成日期应大于等于执行的最大完成日期:2022-10-26'); // 当项目的完成日期小于执行的完成日期时
r($project->update(20, $beginGtExecutionBegin)) && p('message:begin') && e('项目的开始日期应小于等于执行的最小开始日期:2022-07-07'); // 当项目的开始日期大于执行的开始日期时
$withoutProductProject = $data;
$withoutProductProject['hasProduct'] = '';
$withoutProductProject['name'] = '测试更新影子产品';
$withoutProductProject['status'] = 'closed';
r($project->update(2, $normalProject)) && p('name') && e('测试更新项目十'); // 正常更新项目的情况
r($project->update(2, $noProductProject)) && p('message:0') && e('最少关联一个产品'); // 未关联产品的项目
r($project->update(2, $emptyTitleProject)) && p('message[name]:0') && e('『项目名称』不能为空。'); // 更新项目名称为空时
r($project->update(2, $emptyBeginProject)) && p('message[begin]:0') && e('『计划开始』不能为空。'); // 当计划完成为空时更新项目信息
r($project->update(2, $emptyEndProject)) && p('message:end') && e('项目的完成日期应大于等于执行的最大完成日期:2022-10-26'); // 当项目的完成日期小于执行的完成日期时
r($project->update(2, $beginGtExecutionBegin)) && p('message:begin') && e('项目的开始日期应小于等于执行的最小开始日期:2022-07-07'); // 当项目的开始日期大于执行的开始日期时
r($project->update(2, $withoutProductProject)) && p('name') && e('测试更新影子产品'); // 无产品项目
@@ -0,0 +1,23 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/project.class.php';
su('admin');
/**
title=测试 projectModel::updateTeamMembersByProject();
cid=1
pid=1
*/
global $tester;
$tester->app->loadConfig('execution');
$project = new Project();
$oldProject = new stdclass();
$data = new stdclass();
r($project->updateTeamMembersByProject(1, $data, $oldProject)) && p('') && e(1); // 更新关联的成员列表。
r($project->updateTeamMembersByProject(2, $data, $oldProject)) && p('') && e(1); // 更新关联的成员列表。
@@ -0,0 +1,23 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/project.class.php';
su('admin');
/**
title=测试 projectModel::updateUserViewByProject();
cid=1
pid=1
*/
global $tester;
$tester->app->loadConfig('execution');
$project = new Project();
$oldProject = new stdclass();
$data = new stdclass();
r($project->updateUserViewByProject(1, '') && p('') && e(1); // 更新项目关联的用户视图。
r($project->updateUserViewByProject(2, 'open') && p('') && e(1); // 更新项目关联的用户视图。
@@ -0,0 +1,23 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/project.class.php';
su('admin');
/**
title=测试 projectModel::updateWhitelistByProject();
cid=1
pid=1
*/
global $tester;
$tester->app->loadConfig('execution');
$project = new Project();
$oldProject = new stdclass();
$data = new stdclass();
r($project->updateWhitelistByProject(1, $data, $oldProject) && p('') && e(1); // 更新关联的白名单列表。
r($project->updateWhitelistByProject(2, $data, $oldProject) && p('') && e(1); // 更新关联的白名单列表。
@@ -0,0 +1,28 @@
title: zt_project
author: liuhong
version: "1.0"
fields:
- field: id
range: 1-6
- field: project
range: 0
- field: model
range: ', , ,scrum, ,scrum,scrum'
- field: type
range: 'program,program,project,program,project,project'
- field: status
range: 'wait,doing,closed,suspended'
- field: parent
range: 0, 1, 2, 0, 3, 0
- field: grade
range: 1, 2, 3, 1, 2, 1
- field: path
range: '`,1,`, `,1,2,`, `,1,2,3,`, `,4,`, `,4,5,`, `,6,`'
- field: name
range: '项目集1, 项目集1-1, 项目1-1-2, 项目集3, 项目3-4, 无迭代无产品项目6'
- field: code
range: 'program1, program1-1, project1-1-2, program3, project3-4, project6'
- field: begin
range: "'2023-05-01', '2023-05-07', '2023-05-20', '2023-04-04', '2023-05-01', '2023-05-08'"
- field: end
range: "'2023-06-01', '2023-05-19', '2023-06-12', '2023-07-03', '2023-06-03', '2023-05-08'"
@@ -1,4 +1,4 @@
title: getstats4kanban-1
title: getstats4kanban-project
author: chaideqing
version: "1.0"
fields:
@@ -0,0 +1,24 @@
title: zt_project
author: guofeilong
version: "1.0"
fields:
- field: id
range: 1-6
- field: project
range: 0, 1, 0, 3, 3, 0
- field: model
range: 'scrum, ,waterfall, , ,scrum'
- field: type
range: 'project,sprint,project,stage,stage,project'
- field: status
range: 'wait,doing,closed,suspended'
- field: parent
range: 0, 1, 0, 3, 3, 0
- field: path
range: '`,1,`, `,1,2,`, `,3,`, `,3,4,`, `,3,4,5,`, `,6,`'
- field: hasProduct
range: 1, 1, 1, 1, 1, 0
- field: name
range: '项目1, 执行1-2, 项目3, 执行3-4, 执行3-5, 无迭代无产品项目6'
- field: code
range: 'project1, sprint1-2, project3, sprint3-4, sprint3-5, project6'
@@ -0,0 +1,24 @@
title: zt_project
author: guofeilong
version: "1.0"
fields:
- field: id
range: 1-6
- field: project
range: 0, 1, 0, 3, 3, 0
- field: model
range: 'scrum, ,waterfall, , ,scrum'
- field: type
range: 'project,sprint,project,stage,stage,project'
- field: status
range: 'wait,doing,closed,suspended'
- field: parent
range: 0, 1, 0, 3, 3, 0
- field: path
range: '`,1,`, `,1,2,`, `,3,`, `,3,4,`, `,3,4,5,`, `,6,`'
- field: hasProduct
range: 1, 1, 1, 1, 1, 0
- field: name
range: '项目1, 执行1-2, 项目3, 执行3-4, 执行3-5, 无迭代无产品项目6'
- field: code
range: 'project1, sprint1-2, project3, sprint3-4, sprint3-5, project6'
@@ -0,0 +1,24 @@
title: zt_project
author: guofeilong
version: "1.0"
fields:
- field: id
range: 1-6
- field: project
range: 0, 1, 0, 3, 3, 0
- field: model
range: 'scrum, ,waterfall, , ,scrum'
- field: type
range: 'project,sprint,project,stage,stage,project'
- field: status
range: 'wait,doing,closed,suspended'
- field: parent
range: 0, 1, 0, 3, 3, 0
- field: path
range: '`,1,`, `,1,2,`, `,3,`, `,3,4,`, `,3,4,5,`, `,6,`'
- field: hasProduct
range: 1, 1, 1, 1, 1, 0
- field: name
range: '项目1, 执行1-2, 项目3, 执行3-4, 执行3-5, 无迭代无产品项目6'
- field: code
range: 'project1, sprint1-2, project3, sprint3-4, sprint3-5, project6'
@@ -0,0 +1,24 @@
title: zt_project
author: guofeilong
version: "1.0"
fields:
- field: id
range: 1-6
- field: project
range: 0, 1, 0, 3, 3, 0
- field: model
range: 'scrum, ,waterfall, , ,scrum'
- field: type
range: 'project,sprint,project,stage,stage,project'
- field: status
range: 'wait,doing,closed,suspended'
- field: parent
range: 0, 1, 0, 3, 3, 0
- field: path
range: '`,1,`, `,1,2,`, `,3,`, `,3,4,`, `,3,4,5,`, `,6,`'
- field: hasProduct
range: 1, 1, 1, 1, 1, 0
- field: name
range: '项目1, 执行1-2, 项目3, 执行3-4, 执行3-5, 无迭代无产品项目6'
- field: code
range: 'project1, sprint1-2, project3, sprint3-4, sprint3-5, project6'
@@ -0,0 +1,24 @@
title: zt_project
author: guofeilong
version: "1.0"
fields:
- field: id
range: 1-6
- field: project
range: 0, 1, 0, 3, 3, 0
- field: model
range: 'scrum, ,waterfall, , ,scrum'
- field: type
range: 'project,sprint,project,stage,stage,project'
- field: status
range: 'wait,doing,closed,suspended'
- field: parent
range: 0, 1, 0, 3, 3, 0
- field: path
range: '`,1,`, `,1,2,`, `,3,`, `,3,4,`, `,3,4,5,`, `,6,`'
- field: hasProduct
range: 1, 1, 1, 1, 1, 0
- field: name
range: '项目1, 执行1-2, 项目3, 执行3-4, 执行3-5, 无迭代无产品项目6'
- field: code
range: 'project1, sprint1-2, project3, sprint3-4, sprint3-5, project6'
@@ -0,0 +1,24 @@
title: zt_project
author: guofeilong
version: "1.0"
fields:
- field: id
range: 1-6
- field: project
range: 0, 1, 0, 3, 3, 0
- field: model
range: 'scrum, ,waterfall, , ,scrum'
- field: type
range: 'project,sprint,project,stage,stage,project'
- field: status
range: 'wait,doing,closed,suspended'
- field: parent
range: 0, 1, 0, 3, 3, 0
- field: path
range: '`,1,`, `,1,2,`, `,3,`, `,3,4,`, `,3,4,5,`, `,6,`'
- field: hasProduct
range: 1, 1, 1, 1, 1, 0
- field: name
range: '项目1, 执行1-2, 项目3, 执行3-4, 执行3-5, 无迭代无产品项目6'
- field: code
range: 'project1, sprint1-2, project3, sprint3-4, sprint3-5, project6'
+18
View File
@@ -252,4 +252,22 @@ class Project
$projects = $this->project->fetchProjectListByQuery($queryType, $param, $orderBy, 15, '');
return $projects;
}
/**
* testCreateProduct
*
* @param int $projectID
* @param object $project
* @param object $postData
* @param object $program
* @access public
* @return string|array
*/
public function testCreateProduct($projectID, $project, $postData, $program)
{
$result = $this->project->createProduct($projectID, $project, $postData, $program);
if(!$result) return dao::getError();
return '1';
}
}
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
su('admin');
zdTable('project')->config('project')->gen(2);
/**
title=测试 projectModel->accessDenied();
timeout=0
cid=1
- 执行$_SESSION['project'] @2
- 执行$_SESSION['project'] @0
*/
global $tester;
$tester->loadModel('project');
$tester->project->saveState(2, array(2 => ''));
r($_SESSION['project']) && p() && e('2');
$tester->project->accessDenied();
r($_SESSION['project']) && p() && e('0');
+1 -1
View File
@@ -4,7 +4,7 @@ include dirname(__FILE__, 5) . "/test/lib/init.php";
su('admin');
zdTable('project')->config('1')->gen(8);
zdTable('project')->config('project')->gen(8);
/**
+16 -18
View File
@@ -12,24 +12,15 @@ $program->code->range('项目集代号');
$program->desc->range('测试项目集');
$program->gen(1);
zdTable('team')->gen(0);
zdTable('product')->gen(0);
/**
title=测试 projectModel->create();
timeout=0
cid=1
- 执行projectClass模块的create方法,参数是$normalProject, $postData属性name @测试新增项目一
- 执行projectClass模块的create方法,参数是$emptyNameProject, $postData @『项目名称』不能为空。
- 执行projectClass模块的create方法,参数是$emptyEndProject, $postData @『计划完成』不能为空。
- 执行projectClass模块的create方法,参数是$beginGtEndProject, $postData @『计划完成』应当大于『2022-02-07』。
- 执行projectClass模块的create方法,参数是$emptyBeginProject, $postData @『计划开始』不能为空。
*/
global $tester;
@@ -54,16 +45,23 @@ $project->hasProduct = 1;
$project->openedBy = 'admin';
$project->openedDate = '2023-01-01';
$emptyNameProject = clone $project;
unset($emptyNameProject->name);
$hasProductProject = clone $project;
$hasProductProject->hasProduct = 1;
$hasProductProject->name = '测试新增产品一';
$postData = new stdclass();
$postData->rawdata = clone $project;
$postData->rawdata->uid = '64dda2xc';
$postData->rawdata->delta = 0;
$postData->rawdata->products = array(1);
$projectID = $projectClass->create($normalProject, $postData);
$project = $projectClass->create($project, $postData);
$projectID = $project->id;
r($projectClass->createProduct($projectID, $project, $postData, $program)) && p('name') && e('测试新增项目一');
//r($projectClass->createProduct($emptyNameProject, $postData)) && p('message[name]:0') && e('『项目名称』不能为空。');
//r($projectClass->createProduct($emptyEndProject, $postData)) && p('message[end]:0') && e('『计划完成』不能为空。');
//r($projectClass->createProduct($beginGtEndProject, $postData)) && p('message[end]:0') && e('『计划完成』应当大于『2022-02-07』。');
//r($projectClass->createProduct($emptyBeginProject, $postData)) && p('message[begin]:0') && e('『计划开始』不能为空。');
r($projectClass->testCreateProduct($projectID, $project, $postData, $program)) && p() && e('1');
r($projectClass->testCreateProduct($projectID, $emptyNameProject, $postData, $program)) && p('name:0') && e('『产品名称』不能为空。');
r($projectClass->testCreateProduct($projectID, $project, $postData, $program)) && p('name:0') && e('『产品名称』已经有『测试新增项目一』这条记录了。');
r($projectClass->testCreateProduct($projectID, $hasProductProject, $postData, $program)) && p() && e('1');
@@ -0,0 +1,22 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/project.class.php';
su('admin');
/**
title=测试 projectTao::deleteextramembersbyproject();
timeout=0
cid=1
- 执行project模块的doSuspend方法,参数是2, $project @1
*/
global $tester;
$tester->loadModel('project');
$project = new stdClass;
$team = array();
$members = array();
r($tester->project->deleteextramembersbyproject(2, $project, $team, $members)) && p() && e(1);
@@ -1,17 +1,21 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
su('admin');
$project = zdTable('project');
$project->type->range('sprint');
$project->gen(8);
zdTable('project')->config('project')->gen(8);
/**
title=测试 projectTao::getLatestExecutions();
timeout=0
cid=1
PHP Warning: Undefined array key 1 in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Deprecated: substr(): Passing null to parameter #1 ($string) of type string is deprecated in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Warning: Undefined array key 1 in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Deprecated: substr(): Passing null to parameter #1 ($string) of type string is deprecated in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
- 执行executions[3]模块的name方法 @项目3
- 执行executions[3]模块的code方法 @project3
@@ -21,5 +25,6 @@ cid=1
global $tester;
$tester->loadModel('project');
$executions = $tester->project->getLatestExecutions();
r($executions[3]->name) && p() && e('项目3');
r($executions[3]->code) && p() && e('project3');
r($executions[3]->code) && p() && e('project3');
+23 -13
View File
@@ -4,33 +4,43 @@ include dirname(__FILE__, 5) . "/test/lib/init.php";
su('admin');
zdTable('project')->config('1')->gen(8);
zdTable('project')->config('project')->gen(8);
/**
title=测试 projectTao::getProjectsStats()
timeout=0
cid=1
- 执行$projects['myProjects'] @3
- 执行$projects['otherProjects'] @3
PHP Warning: Undefined array key 1 in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Deprecated: substr(): Passing null to parameter #1 ($string) of type string is deprecated in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Warning: Undefined array key 1 in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Deprecated: substr(): Passing null to parameter #1 ($string) of type string is deprecated in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Warning: Undefined array key 1 in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Deprecated: substr(): Passing null to parameter #1 ($string) of type string is deprecated in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Warning: Undefined array key 1 in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
PHP Deprecated: substr(): Passing null to parameter #1 ($string) of type string is deprecated in /opt/zentao/gitlab/zentaopms/test/lib/init.php on line 317
- 执行$projects['my'] @3
- 执行projects['myProjects'][1]['wait'][0]模块的status方法 @wait
- 执行$projects['other'] @3
- 执行projects['myProjects'][3]['doing'][0]模块的status方法 @doing
- 执行projects['my'][1]['wait'][0]模块的status方法 @wait
- 执行projects['otherProjects'][2]['wait'][0]模块的status方法 @wait
- 执行projects['my'][3]['doing'][0]模块的status方法 @doing
- 执行projects['other'][2]['wait'][0]模块的status方法 @wait
- 执行projects['other'][4]['doing'][0]模块的status方法 @doing
- 执行projects['otherProjects'][4]['doing'][0]模块的status方法 @doing
*/
global $tester;
$tester->loadModel('project');
$projects = $tester->project->getProjectsStats();
r(count($projects['myProjects'])) && p() && e('3');
r(count($projects['otherProjects'])) && p() && e('3');
r($projects['myProjects'][1]['wait'][0]->status) && p() && e('wait');
r($projects['myProjects'][3]['doing'][0]->status) && p() && e('doing');
r($projects['otherProjects'][2]['wait'][0]->status) && p() && e('wait');
r($projects['otherProjects'][4]['doing'][0]->status) && p() && e('doing');
r(count($projects['my'])) && p() && e('3');
r(count($projects['other'])) && p() && e('3');
r($projects['my'][1]['wait'][0]->status) && p() && e('wait');
r($projects['my'][3]['doing'][0]->status) && p() && e('doing');
r($projects['other'][2]['wait'][0]->status) && p() && e('wait');
r($projects['other'][4]['doing'][0]->status) && p() && e('doing');
@@ -4,7 +4,7 @@ include dirname(__FILE__, 5) . "/test/lib/init.php";
su('admin');
zdTable('project')->config('1')->gen(8);
zdTable('project')->config('project')->gen(8);
/**
@@ -27,4 +27,4 @@ $projectsStats = $tester->project->classfyProjects($projectsStats);
$projectsStats = $tester->project->sortAndReduceClosedGroup($projectsStats, 2);
r(count($projectsStats['my'][3]['closed'])) && p() && e('1');
r(count($projectsStats['other'][4]['closed'])) && p() && e('1');
r(count($projectsStats['other'][4]['closed'])) && p() && e('1');
@@ -0,0 +1,8 @@
title: accessdenied-project
author: chaideqing
version: "1.0"
fields:
- field: id
range: '2,3'
- field: type
range: 'project'
@@ -1,4 +1,4 @@
title: classfyprojects-1
title: classfyprojects-project
author: chaideqing
version: "1.0"
fields:
@@ -0,0 +1,24 @@
title: zt_project
author: guofeilong
version: "1.0"
fields:
- field: id
range: 1-6
- field: project
range: 0, 1, 0, 3, 3, 0
- field: model
range: 'scrum, ,waterfall, , ,scrum'
- field: type
range: 'project,sprint,project,stage,stage,project'
- field: status
range: 'wait,doing,closed,suspended'
- field: parent
range: 0, 1, 0, 3, 3, 0
- field: path
range: '`,1,`, `,1,2,`, `,3,`, `,3,4,`, `,3,4,5,`, `,6,`'
- field: hasProduct
range: 1, 1, 1, 1, 1, 0
- field: name
range: '项目1, 执行1-2, 项目3, 执行3-4, 执行3-5, 无迭代无产品项目6'
- field: code
range: 'project1, sprint1-2, project3, sprint3-4, sprint3-5, project6'
@@ -0,0 +1,6 @@
title: getlatestexecutions-project
author: chaideqing
version: "1.0"
fields:
- field: type
range: 'sprint'
@@ -1,4 +1,4 @@
title: getprojectsstats-1
title: getprojectsstats-project
author: chaideqing
version: "1.0"
fields:
@@ -1,4 +1,4 @@
title: sortandreduceclosedgroup-1
title: sortandreduceclosedgroup-project
author: chaideqing
version: "1.0"
fields:
+9 -4
View File
@@ -9,7 +9,7 @@ foreach($lang->project->modelList as $key => $text)
$projectModelItems[] = array
(
'active' => ($key == $model),
'url' => '',
'url' => $this->createLink("project", "create", "model=$key&programID=$programID&copyProjectID=0&extra=productID=$productID,branchID=$branchID"),
'text' => $text,
'data-type' => 'ajax'
);
@@ -110,12 +110,16 @@ formPanel
set::seg(true),
btn
(
setClass('primary-pale'),
setClass('primary-pale project-type-1'),
on::click('changeType(1)'),
$lang->project->projectTypeList[1]
),
btn($lang->project->projectTypeList[0])
btn(
setClass('project-type-0'),
on::click('changeType(0)'),
$lang->project->projectTypeList[0]
)
),
/* TODO change value with button click event */
formHidden('hasProduct', 1)
),
formGroup
@@ -215,6 +219,7 @@ formPanel
formGroup
(
set::width('1/2'),
set('id', 'linkProduct'),
set::label($lang->project->manageProducts),
inputGroup
(
+4 -4
View File
@@ -230,11 +230,11 @@
</tr>
<?php endif;?>
<?php if($model == 'waterfall' or $model == 'waterfallplus'):?>
<tr class='hide division'>
<th><?php echo $lang->project->division;?></th>
<tr class='hide stageBy'>
<th><?php echo $lang->project->stageBy;?></th>
<td>
<?php echo html::radio('division', $lang->project->divisionList, '0');?>
<icon class='icon icon-help' data-toggle='popover' data-trigger='focus hover' data-placement='right' data-tip-class='text-muted popover-sm' data-content="<?php echo $lang->project->divisionTips;?>"></icon>
<?php echo html::radio('stageBy', $lang->project->stageByList, '0');?>
<icon class='icon icon-help' data-toggle='popover' data-trigger='focus hover' data-placement='right' data-tip-class='text-muted popover-sm' data-content="<?php echo $lang->project->stageByTips;?>"></icon>
</td>
</tr>
<?php endif;?>
+5 -5
View File
@@ -188,13 +188,13 @@
<?php endforeach;?>
<?php endif;?>
<?php if($project->model == 'waterfall' or $project->model == 'waterfallplus'):?>
<?php $class = (!$project->division and count($linkedProducts) < 2) ? 'hide' : '';?>
<?php $class = ($project->stageBy == 'product' and count($linkedProducts) < 2) ? 'hide' : '';?>
<?php $disabled = !empty($executions) ? "disabled='disabled'" : '';?>
<tr class='<?php echo $class;?> division'>
<th><?php echo $lang->project->division;?></th>
<tr class='<?php echo $class;?> stageBy'>
<th><?php echo $lang->project->stageBy;?></th>
<td colspan='3'>
<?php echo html::radio('division', $lang->project->divisionList, $project->division, $disabled);?>
<icon class='icon icon-help' data-toggle='popover' data-trigger='focus hover' data-placement='right' data-tip-class='text-muted popover-sm' data-content="<?php echo $lang->project->divisionTips;?>"></icon>
<?php echo html::radio('stageBy', $lang->project->stageByList, $project->stageBy == 'project', $disabled);?>
<icon class='icon icon-help' data-toggle='popover' data-trigger='focus hover' data-placement='right' data-tip-class='text-muted popover-sm' data-content="<?php echo $lang->project->stageByTips;?>"></icon>
</td>
</tr>
<?php endif;?>
+3 -3
View File
@@ -16,7 +16,7 @@
<?php js::set('changeStatusHtml', $changeStatusHtml);?>
<div id='mainMenu' class='clearfix'>
<div class='btn-toolbar pull-left'>
<?php if($project->division and $project->hasProduct):?>
<?php if($project->stageBy == 'project' and $project->hasProduct):?>
<div class='btn-group'>
<?php $viewName = $productID != 0 ? zget($productList, $productID) : $lang->product->allProduct;?>
<a href='javascript:;' class='btn btn-link btn-limit text-ellipsis' data-toggle='dropdown' style="max-width: 120px;"><span class='text' title='<?php echo $viewName;?>'><?php echo $viewName;?></span> <span class='caret'></span></a>
@@ -98,7 +98,7 @@
<?php endif;?>
</div>
</th>
<?php if($project->division and $project->hasProduct) echo "<th class='text-left w-100px'>{$lang->project->product}</th>";?>
<?php if($project->stageBy == 'project' and $project->hasProduct) echo "<th class='text-left w-100px'>{$lang->project->product}</th>";?>
<th class='c-status text-center'><?php echo $lang->project->status;?></th>
<th class='w-50px'><?php echo $lang->execution->owner;?></th>
<th class='c-date'><?php echo $lang->programplan->begin;?></th>
@@ -113,7 +113,7 @@
</thead>
<tbody id="executionTableList">
<?php foreach($executionStats as $execution):?>
<?php $execution->division = $project->division;?>
<?php $execution->stageBy = $project->stageBy;?>
<?php $executionProductID = (empty($productID) and !empty($execution->product)) ? $execution->product : $productID;?>
<?php $this->execution->printNestedList($execution, false, $users, $executionProductID);?>
<?php endforeach;?>
+2 -2
View File
@@ -140,9 +140,9 @@
</div>
</div>
<?php $noticeSwitch = (!$project->division and count($linkedProducts) == 1 and empty($executions));?>
<?php $noticeSwitch = ($project->stageBy == 'product' and count($linkedProducts) == 1 and empty($executions));?>
<?php js::set('linkedProducts', array_keys($linkedProducts));?>
<?php js::set('noticeSwitch', $noticeSwitch);?>
<?php js::set('noticeDivsion', $lang->project->noticeDivsion);?>
<?php js::set('divisionSwitchList', $lang->project->divisionSwitchList);?>
<?php js::set('stageBySwitchList', $lang->project->stageBy == 'project'SwitchList);?>
<?php include '../../common/view/footer.html.php';?>
+87 -8
View File
@@ -50,14 +50,55 @@ class projectZen extends project
return $project;
}
/**
* Judge products not empty.
*
* @param array $products
* @access protected
* @return bool
*/
protected function checkProductsNotEmpty($products): bool
{
$linkedProductsCount = 0;
foreach($products as $product)
{
if(!empty($product)) $linkedProductsCount++;
}
if(empty($linkedProductsCount))
{
dao::$errors[] = $this->lang->project->errorNoProducts;
return false;
}
return true;
}
/**
* Check work days legtimate.
*
* @param object $project
* @access protected
* @return bool
*/
protected function checkWorkdaysLegtimate($project): bool
{
$workdays = helper::diffDate($project->end, $project->begin) + 1;
if(isset($project->days) and $project->days > $workdays)
{
dao::$errors['days'] = sprintf($this->lang->project->workdaysExceed, $workdays);
return false;
}
return true;
}
/**
* Append extras data to post data.
*
* @param int $projectID
* @param object $postData
* @access protected
* @return int|object
* @return object|false
*/
protected function prepareEditExtras(object $postData): object
protected function prepareEditExtras(int $projectID, object $postData): object|false
{
$project = $postData->setDefault('team', $this->post->name)
->setDefault('lastEditedBy', $this->app->user->account)
@@ -65,18 +106,24 @@ class projectZen extends project
->setDefault('days', '0')
->setIF($this->post->delta == 999, 'end', LONG_TIME)
->setIF($this->post->delta == 999, 'days', 0)
->setIF($this->post->begin == '0000-00-00', 'begin', '')
->setIF($this->post->end == '0000-00-00', 'end', '')
->setIF($this->post->future, 'budget', 0)
->setIF($this->post->budget != 0, 'budget', round((float)$this->post->budget, 2))
->stripTags($this->config->project->editor->edit['id'], $this->config->allowedTags)
->get();
/* Check if products and branch valid by project.*/
if($project->product && !$this->project->checkBranchAndProductValid($projectID, $project)) return false;
/* Check if products not empty.*/
if(!$this->checkProductsNotEmpty($project->products)) return false;
/* Check if work days legtimate.*/
if(!$this->checkWorkdaysLegtimate($project)) return false;
if(!isset($this->config->setCode) or $this->config->setCode == 0) unset($project->code);
/* Lean mode relation defaultProgram. */
/* Lean mode relation defaultProgram.*/
if($this->config->systemMode == 'light') $project->parent = $this->config->global->defaultProgram;
return $project;
}
@@ -664,8 +711,8 @@ class projectZen extends project
$diffProducts = array_merge(array_diff($oldProductIDs, $newProductIDs), array_diff($newProductIDs, $oldProductIDs));
if($diffProducts) $this->loadModel('action')->create('project', $projectID, 'Managed', '', !empty($postData->rawdata->products) ? implode(',', $postData->rawdata->products) : '');
/* Division project update linked products. */
if(empty($project->division))
/* Project stageBy project update linked products. */
if($project->stageBy == 'project')
{
foreach($idList as $executionID)
{
@@ -870,4 +917,36 @@ class projectZen extends project
$this->project->setMenu($projectID);
}
}
/**
* 构建项目集下拉列表数据
* build Select Form for program
*
* @param int $selectedProgramID
* @param object $selectedProgram
* @param string $objectType
*
* @access protected
* @return array
*/
protected function buildSelectForm(int $selectedProgramID, object $selectedProgram, string $objectType): array
{
if(isset($selectedProgram))
{
$data['selectedProgramBegin'] = $selectedProgram->begin;
$data['selectedProgramEnd'] = $selectedProgram->end;
$data['budgetUnit'] = $selectedProgram->budgetUnit;
$data['selectedProgramPath'] = explode(',', $selectedProgram->path);
}
if($objectType == 'program')
{
$withProgram = $this->config->systemMode == 'ALM' ? true : false;
$allProducts = array(0 => '') + $this->program->getProductPairs($selectedProgramID, 'all', 'noclosed', '', 0, $withProgram);
$data['allProducts'] = html::select("products[]", $allProducts, '', "class='form-control chosen' onchange='loadBranches(this)'");
$data['plans'] = html::select('plans[][][]', '', '', 'class=\'form-control chosen\' multiple');
}
return $data;
}
}
+6 -8
View File
@@ -111,7 +111,7 @@ class task extends control
$storyID = (int)$storyID;
$moduleID = (int)$moduleID;
$taskID = (int)$taskID;
$extra = str_replace(array(',', ' '), array('&', ''), $extra);
$extra = str_replace(array(',', ' '), array('&', ''), $extra);
parse_str($extra, $output);
/* 判断不能访问的执行。 TODO: 提示语应改为执行。 */
@@ -125,16 +125,14 @@ class task extends control
if(!empty($_POST))
{
/* Form data. */
$postData = form::data($this->config->task->form->batchCreate);
$mails = $this->task->batchCreate($executionID, $postData, $output);
/* 批量创建任务。 */
$postData = form::data($this->config->task->form->batchCreate);
$tasks = $this->taskZen->prepareTasks4BatchCreate($execution, $postData->rawdata);
$taskIdList = $this->task->batchCreate($execution, $tasks, $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));
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);
+97 -128
View File
@@ -17,17 +17,22 @@ class taskModel extends model
* 创建一个任务。
* Create a task.
*
* @param int $executionID
* @param int $bugID
* @param object $rawData
* @param array $data
* @param bool $selectTestStory
* @param int $executionID
* @param int $bugID
* @param object $rawData
* @param array $data
* @param bool $selectTestStory
* @param array $teamSourceList
* @param array $teamEstimateList
* @param array|bool $teamConsumedList
* @param array|bool $teamLeftList
* @access public
* @return bool|array
*/
public function create(object $task, array $assignedToList, int $multiple, array $team, bool $selectTestStory): bool|array
public function create(object $task, array $assignedToList, int $multiple, array $team, bool $selectTestStory, array $teamSourceList, array $teamEstimateList, array|bool $teamConsumedList, array|bool $teamLeftList): bool|array
{
$this->loadModel('action');
/* Remove required fields for creating tasks based on conditions. */
$this->taskTao->removeCreateRequiredFields($task, $selectTestStory);
@@ -54,13 +59,12 @@ class taskModel extends model
if($multiple and count(array_filter($team)) > 1)
{
$task->id = $taskID;
$teams = $this->manageTaskTeam($task->mode, $taskID, 'wait');
$teams = $this->manageTaskTeam($task->mode, $task, $team, $teamSourceList, $teamEstimateList, $teamConsumedList, $teamLeftList);
if($teams) $this->computeHours4Multiple($task);
unset($task->id);
}
$taskIdList[] = $taskID;
$this->action->create('task', $taskID, 'Opened', '');
}
return $taskIdList;
@@ -132,156 +136,60 @@ class taskModel extends model
/**
* Batch create tasks.
*
* @param int $executionID
* @param object $postData
* @param object $execution
* @param array $tasks
* @param array $output
* @access public
* @return void
* @return array|false
*/
public function batchCreate(int $executionID, object $postData, array $output)
public function batchCreate(object $execution, array $tasks, array $output): array|false
{
$tasks = $postData->rawdata;
/* 检查必填项。 */
$tasks = $this->taskTao->checkRequired4BatchCreate($execution, $tasks);
if(!$tasks) return false;
/* Load module and init vars. */
$this->loadModel('common');
$this->loadModel('story');
$this->loadModel('score');
$this->loadModel('action');
$this->loadModel('kanban');
$mails = array();
/* 运营管理界面移除泳道和泳道列。 */
if($this->config->vision == 'lite')
{
$lanes = $tasks->lane;
$columns = $tasks->column;
unset($tasks->lane);
unset($tasks->column);
}
/* Judge whether the current task is a parent. */
$parentID = !empty($tasks->parent[1]) ? $tasks->parent[1] : 0;
$oldParentTask = $this->dao->findById((int)$parentID)->from(TABLE_TASK)->fetch();
/* 去除重复数据。 */
$tasks = $this->taskTao->removeDuplicate4BatchCreate($executionID, $tasks);
if(!$tasks) return false;
/* Init vars. */
$story = 0;
$module = 0;
$type = '';
$assignedTo = '';
$estStarted = null;
$deadline = null;
/* Get task data. */
$extendFields = $this->getFlowExtendFields();
$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];
$assignedTo = (!isset($tasks->assignedTo[$i]) or $tasks->assignedTo[$i] == 'ditto') ? $assignedTo : $tasks->assignedTo[$i];
$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))
{
dao::$errors['message'][] = sprintf($this->lang->error->notempty, $this->lang->task->name);
return false;
}
continue;
}
/* 构建任务数据。 */
$dittoFields = array('story' => $story, 'module' => $module, 'type' => $type, 'assignedTo' => $assignedTo, 'estStarted' => $estStarted, 'deadline' => $deadline);
$data = $this->taskTao->constructData4BatchCreate($execution, $tasks, $i, $dittoFields, $extendFields);
}
/* 检查必填项。 */
$data = $this->taskTao->checkRequired4BatchCreate($execution, $data);
if(!$data) return false;
$childTasks = '';
foreach($data as $i => $task)
$taskIdList = array();
foreach($tasks as $i => $task)
{
/* 获取当前任务所属泳道并移除laneID属性。 */
$laneID = isset($output['laneID']) ? $output['laneID'] : 0;
if(isset($task->laneID))
{
$laneID = $task->laneID;
unset($task->laneID);
}
$laneID = isset($task->laneID) ? $task->laneID : $laneID;
if(isset($task->laneID)) unset($task->laneID);
/* 将数据插入到任务表中。 */
$task->version = 1;
$taskID = $this->taskTao->doCreate($task);
if(!$taskID) return false;
/* 获取子任务ID。*/
$childTasks .= $taskID . ',';
/* 设置相关需求阶段。 */
/* 更新任务相关信息。 */
if($task->story) $this->story->setStage($task->story);
$columnID = isset($output['columnID']) ? $output['columnID'] : 0;
$this->updateKanban4BatchCreate($taskID, $execution->id, $laneID, $columnID);
$this->executeHooks($taskID);
/* 更新看板数据。 */
if($this->config->vision == 'lite')
{
$this->kanban->addKanbanCell($executionID, $lanes[$i], $columns[$i], 'task', $taskID);
}
else
{
$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($executionID, $laneID, $columnID, 'task', $taskID);
}
/* 记录日志并计算积分。 */
$actionID = $this->action->create('task', $taskID, 'Opened', '');
$this->action->create('task', $taskID, 'Opened', '');
if(!dao::isError()) $this->score->create('task', 'create', $taskID);
/* 将taskID和actionID存入数组中。 */
$mails[$i] = new stdclass();
$mails[$i]->taskID = $taskID;
$mails[$i]->actionID = $actionID;
$taskIdList[$taskID] = $taskID;
}
if(!dao::isError()) $this->score->create('ajax', 'batchCreate');
/* 处理任务拆分的情况。 */
if($oldParentTask and !empty($taskID))
{
/* 当一个普通任务有消耗时,拆分子任务。 */
/* When common task are child tasks and the common task has consumption, create a child task. */
if($oldParentTask->parent == 0 and $oldParentTask->consumed > 0)
{
$splitResult = $this->taskTao->splitConsumedTask($oldParentTask);
if(!$splitResult) return false;
}
$this->updateParentStatus($taskID);
$this->computeBeginAndEnd($parentID);
$this->taskTao->updateParentAfterSplit($parentID);
/* 记录父任务日志。 */
$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);
}
$childTasks = !empty($parentID) ? implode(',', $taskIdList) : '';
if($parentID) $this->afterSplitTask($oldParentTask, $childTasks);
/* 更新当前执行下的任务泳道。 */
if(!isset($output['laneID']) or !isset($output['columnID']) or !isset($lanes)) $this->kanban->updateLane($executionID, 'task');
return $mails;
if(!isset($output['laneID']) or !isset($output['columnID']) or !isset($lanes)) $this->kanban->updateLane($execution->id, 'task');
return $taskIdList;
}
/**
@@ -994,7 +902,7 @@ class taskModel extends model
if($task->status == 'closed') $task->assignedTo = 'closed';
if(isset($task->assignedTo) and $oldTask->assignedTo != $task->assignedTo) $task->assignedDate = $now;
if(strpos(',doing,pause,', $task->status) and empty($teams) and empty($task->left) and $task->parent >= 0)
if(strpos(',doing,pause,', $task->status) and empty($oldTask->mode) and empty($task->left) and $task->parent >= 0)
{
dao::$errors[] = sprintf($this->lang->task->error->leftEmptyAB, zget($this->lang->task->statusList, $task->status));
return false;
@@ -1133,7 +1041,7 @@ class taskModel extends model
$isMax = $this->config->edition == 'max';
foreach($tasks as $taskID => $task)
{
if(strpos(',doing,pause,', $task->status) && empty($teams) && $task->parent >= 0 && empty($task->left))
if(strpos(',doing,pause,', $task->status) && empty($oldTask->mode) && $task->parent >= 0 && empty($task->left))
{
dao::$errors[] = sprintf($this->lang->task->error->leftEmpty, $taskID, $this->lang->task->statusList[$task->status]);
return false;
@@ -3566,7 +3474,7 @@ class taskModel extends model
}
}
$list .= '</td>';
if(!empty($execution->division) and $execution->hasProduct) $list .= '<td></td>';
if($execution->stageBy == 'product' and $execution->hasProduct) $list .= '<td></td>';
$list .= "<td class='status-{$task->status} text-center'>" . $this->processStatus('task', $task) . '</td>';
$list .= '<td>' . zget($users, $task->assignedTo, '') . '</td>';
$list .= helper::isZeroDate($task->estStarted) ? '<td class="c-date"></td>' : '<td class="c-date">' . $task->estStarted . '</td>';
@@ -3927,6 +3835,67 @@ class taskModel extends model
return true;
}
/**
* 拆分任务后更新其他数据。
* Process other data after split task.
*
* @param object $oldParentTask
* @param string $childTasks
* @access public
* @return bool
*/
public function afterSplitTask(object $oldParentTask, string $childTasks = ''): bool
{
$parentID = (int)$oldParentTask->id;
/* 当一个普通任务有消耗时,拆分子任务。 */
/* When common task are child tasks and the common task has consumption, create a child task. */
if($oldParentTask->parent == 0 and $oldParentTask->consumed > 0)
{
$taskID = $this->taskTao->splitConsumedTask($oldParentTask);
if(!$taskID) return false;
}
$this->updateParentStatus($taskID);
$this->computeBeginAndEnd($parentID);
$this->taskTao->updateParentAfterSplit($parentID);
/* 记录父任务日志。 */
$newParentTask = $this->dao->findById($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);
return true;
}
/**
* 批量创建任务后的其他数据处理。
* Kanban data processing after batch create tasks.
*
* @param int $taskID
* @param int $executionID
* @param int $laneID
* @param int $columnID
* @access public
* @return void
*/
public function updateKanban4BatchCreate(int $taskID, int $executionID, int $laneID, int $columnID): void
{
$this->loadModel('kanban');
/* 更新看板数据。 */
if($this->config->vision == 'lite')
{
$this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'task', $taskID);
}
else
{
$columnID = $this->kanban->getColumnIDByLaneID($laneID, 'wait');
if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'task', $taskID);
}
}
/**
* 更新看板中的任务泳道数据。
* Update the task lane data in Kanban.
+3 -105
View File
@@ -393,7 +393,7 @@ class taskTao extends taskModel
$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')))
if($execution and ($execution->lifetime == 'ops' or in_array($execution->attribute, array('request', 'review'))))
{
$requiredFields = str_replace(",story,", ',', "$requiredFields");
}
@@ -407,108 +407,6 @@ class taskTao extends taskModel
$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.
@@ -524,6 +422,7 @@ class taskTao extends taskModel
$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, ',');
$requiredFields = array_filter(explode(',', $requiredFields));
/* check data. */
foreach($data as $i => $task)
@@ -550,7 +449,6 @@ class taskTao extends taskModel
}
/* 验证必填字段。 */
$requiredFields = array_filter(explode(',', $requiredFields));
foreach($requiredFields as $field)
{
if(empty($task->$field))
@@ -589,7 +487,7 @@ class taskTao extends taskModel
->exec();
if(dao::isError()) return false;
return true;
return $clonedTaskID;
}
/**
+60 -45
View File
@@ -4,57 +4,72 @@ include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/task.class.php';
su('admin');
zdTable('task')->gen(1);
zdTable('taskspec')->gen(1);
$execution = zdTable('project');
$execution->id->range('1-6');
$execution->name->range('项目1,项目2,迭代1,迭代2,阶段1,阶段2');
$execution->type->range('project{2},sprint{2},stage{2}');
$execution->project->range('0{2},1{2},2{2}');
$execution->lifetime->range('[]{3},ops,[]{2}');
$execution->model->range('scrum,waterfall,[]{4}');
$execution->attribute->range('[]{4},request,review');
$execution->status->range('doing');
$execution->parent->range('0,0,1,1,2');
$execution->grade->range('2{2},1{4}');
$execution->path->range('1,2,`1,3`,`1,4`,`2,5`, `2,6`')->prefix(',')->postfix(',');
$execution->begin->range('20230102 000000:0')->type('timestamp')->format('YY/MM/DD');
$execution->end->range('20230612 000000:0')->type('timestamp')->format('YY/MM/DD');
$execution->gen(6);
/**
title=测试productModel->create();
title=测试taskModel->create();
timeout=0
cid=1
pid=1
测试正常的创建开发任务 >> 开发任务一
测试正常的创建设计任务 >> 11
测试正常的创建需求任务 >> 101
测试正常的创建测试任务 >> test
测试正常的创建研究任务 >> 3
测试正常的创建讨论任务 >> wait
测试正常的创建界面任务 >> admin
测试正常的创建事务任务 >> 2021-01-10
测试正常的创建其他任务 >> 2021-03-19
测试不输入名称创建任务 >> 『任务名称』不能为空。
测试不输入类型创建任务 >> 『任务类型』不能为空。
测试开始时间大于结束时间 >> 『截止日期』应当不小于『2021-04-10』。
测试指派人任务 >> user92
*/
$executionID = '101';
$devel = array('execution' => 3, 'name' => '开发任务一', 'type' => 'devel', 'estimate' => 1, 'version' => 1, 'estStarted' => null, 'deadline' => null);
$design = array('execution' => 3, 'name' => '设计任务一', 'type' => 'design', 'estimate' => 1, 'version' => 1, 'estStarted' => '2023-04-01', 'deadline' => null);
$request = array('execution' => 3, 'name' => '需求任务一', 'type' => 'request', 'estimate' => 1, 'version' => 1, 'estStarted' => null, 'deadline' => '2024-01-01');
$test = array('execution' => 3, 'name' => '测试任务一', 'type' => 'test', 'estimate' => 0, 'version' => 1, 'estStarted' => null, 'deadline' => null);
$study = array('execution' => 3, 'name' => '研究任务一', 'type' => 'study', 'estimate' => 1, 'version' => 1, 'estStarted' => '2023-04-01', 'deadline' => '2024-01-01');
$discuss = array('execution' => 3, 'name' => '讨论任务一', 'type' => 'discuss', 'estimate' => '', 'version' => 1, 'estStarted' => null, 'deadline' => null);
$ui = array('execution' => 3, 'name' => '界面任务一', 'type' => 'ui', 'estimate' => 1, 'version' => 1, 'estStarted' => null, 'deadline' => null);
$affair = array('execution' => 3, 'name' => '事务任务一', 'type' => 'affair', 'estimate' => 1, 'version' => 1, 'estStarted' => null, 'deadline' => null);
$misc = array('execution' => 3, 'name' => '其他任务一', 'type' => 'misc', 'estimate' => 1, 'version' => 1, 'estStarted' => null, 'deadline' => null);
$noexecution = array('execution' => 0, 'name' => '特殊任务一', 'type' => 'devel', 'estStarted' => '2021-04-10', 'deadline' => '2022-03-19', 'estimate' => 1, 'version' => 1);
$noname = array('execution' => 3, 'name' => '', 'type' => 'devel', 'estimate' => 1, 'version' => 1, 'estStarted' => null, 'deadline' => null);
$notype = array('execution' => 3, 'name' => '特殊任务二', 'type' => '', 'estimate' => 1, 'version' => 1, 'estStarted' => null, 'deadline' => null);
$errorEstimate = array('execution' => 3, 'name' => '特殊任务三', 'type' => 'devel', 'estimate' => '2a', 'version' => 1, 'estStarted' => null, 'deadline' => null);
$devel = array('name' => '开发任务一', 'type' => 'devel');
$design = array('name' => '设计任务一', 'type' => 'design');
$request = array('name' => '需求任务一', 'type' => 'request');
$test = array('name' => '测试任务一', 'type' => 'test');
$study = array('name' => '研究任务一', 'type' => 'study');
$discuss = array('name' => '讨论任务一', 'type' => 'discuss');
$ui = array('name' => '界面任务一', 'type' => 'ui');
$affair = array('name' => '事务任务一', 'type' => 'affair');
$misc = array('name' => '其他任务一', 'type' => 'misc');
$noname = array('name' => '', 'type' => 'devel');
$notype = array('name' => '特殊任务一', 'type' => '');
$errortime = array('name' => '特殊任务二', 'type' => 'devel', 'estStarted' => '2021-04-10', 'deadline' => '2021-03-19');
$assignedTo = array('user92');
$assign = array('name' => '指派人user92任务', 'type' => 'devel', 'assignedTo' => $assignedTo);
$assignedToList['empty'] = array();
$assignedToList['single'] = array('admin');
$assignedToList['multiple'] = array('admin', 'user1', 'user2');
$task = new taskTest();
r($task->createObject($devel, $executionID)) && p('name') && e('开发任务一'); // 测试正常的创建开发任务
r($task->createObject($design, $executionID)) && p('project') && e('11'); // 测试正常的创建设计任务
r($task->createObject($request, $executionID)) && p('execution') && e('101'); // 测试正常的创建需求任务
r($task->createObject($test, $executionID)) && p('type') && e('test'); // 测试正常的创建测试任务
r($task->createObject($study, $executionID)) && p('pri') && e('3'); // 测试正常的创建研究任务
r($task->createObject($discuss, $executionID)) && p('status') && e('wait'); // 测试正常的创建讨论任务
r($task->createObject($ui, $executionID)) && p('openedBy') && e('admin'); // 测试正常的创建界面任务
r($task->createObject($affair, $executionID)) && p('estStarted') && e('2021-01-10'); // 测试正常的创建事务任务
r($task->createObject($misc, $executionID)) && p('deadline') && e('2021-03-19'); // 测试正常的创建其他任务
r($task->createObject($noname, $executionID)) && p('name:0') && e('『任务名称』不能为空。'); // 测试不输入名称创建任务
r($task->createObject($notype, $executionID)) && p('type:0') && e('『任务类型』不能为空。'); // 测试不输入类型创建任务
r($task->createObject($errortime, $executionID)) && p('deadline:0') && e('『截止日期』应当不小于『2021-04-10』。'); // 测试开始时间大于结束时间
r($task->createObject($assign, $executionID)) && p('assignedTo') && e('user92'); // 测试指派人任务
$multiple = 1;
$notMultiple = 0;
$teamList = array('admin', 'user1', 'user2');
$selectTestStory = true;
$notSelectTestStory = false;
$requiredFields = 'estimate';
$taskTester = new taskTest();
r($taskTester->createTest($devel, $assignedToList['single'])) && p('name,type,status,assignedTo') && e('开发任务一,devel,wait,admin'); // 测试创建开发类型的任务
r($taskTester->createTest($design, $assignedToList['single'])) && p('name,type,status,assignedTo') && e('设计任务一,design,wait,admin'); // 测试创建设计类型的任务
r($taskTester->createTest($request, $assignedToList['single'])) && p('name,type,status,assignedTo') && e('需求任务一,request,wait,admin'); // 测试创建需求类型的任务
r($taskTester->createTest($test, $assignedToList['single'], $notMultiple, array(), $selectTestStory, array(), array(), false, false, $requiredFields)) && p('name,type,status,assignedTo') && e('测试任务一,test,wait,admin'); // 测试创建测试类型的任务
r($taskTester->createTest($study, $assignedToList['single'])) && p('name,type,status,assignedTo') && e('研究任务一,study,wait,admin'); // 测试创建研究类型的任务
r($taskTester->createTest($discuss, $assignedToList['single'])) && p('name,type,status,assignedTo') && e('讨论任务一,discuss,wait,admin'); // 测试创建讨论类型的任务
r($taskTester->createTest($ui, $assignedToList['single'])) && p('name,type,status,assignedTo') && e('界面任务一,ui,wait,admin'); // 测试创建界面类型的任务
r($taskTester->createTest($affair, $assignedToList['multiple'])) && p('name,type,status,assignedTo') && e('事务任务一,affair,wait,admin'); // 测试创建事务类型的任务
r($taskTester->createTest($misc, $assignedToList['single'])) && p('name,type,status,assignedTo') && e('其他任务一,misc,wait,admin'); // 测试创建其他类型的任务
r($taskTester->createTest($noexecution, $assignedToList['single'])) && p('execution:0') && e('『所属执行』不能为空。'); // 测试所属执行为空的情况
r($taskTester->createTest($noname, $assignedToList['single'])) && p('name:0') && e('『任务名称』不能为空。'); // 测试任务名称为空的情况
r($taskTester->createTest($notype, $assignedToList['single'])) && p('type:0') && e('『任务类型』不能为空。'); // 测试任务类型为空的情况
r($taskTester->createTest($errorEstimate, $assignedToList['single'])) && p('estimate:0') && e('『最初预计』应当是数字,可以是小数。'); // 测试预计工时格式的正确性
@@ -0,0 +1,33 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/task.class.php';
su('admin');
zdTable('project')->config('project')->gen(5);
zdTable('task')->config('task')->gen(8);
zdTable('kanbancolumn')->config('kanbancolumn')->gen(10);
zdTable('kanbancell')->config('kanbancell')->gen(10);
/**
title=taskModel->updateKanban4BatchCreate();
timeout=0
cid=2
*/
$taskIdList = array('1', '2', '3', '4', '5', '6', '7', '8');
$executionIdList = array('2', '3', '4', '5');
$laneIdList = array('1', '2', '3', '4', '5', '6', '7', '8', '9', '0');
$columnIdList = array('1', '2', '3', '4', '5', '6', '7', '8', '9', '0');
$task = new taskTest();
r($task->updateKanban4BatchCreateTest($taskIdList[1], $executionIdList[2], $laneIdList[1], $columnIdList[1])) && p() && e(',2,'); // 综合研发界面下看板项目的任务泳道未开始列无卡片的情况。
r($task->updateKanban4BatchCreateTest($taskIdList[1], $executionIdList[2], $laneIdList[2], $columnIdList[2])) && p() && e(',2,5,6,805,'); // 综合研发界面下看板项目的任务泳道未开始列有卡片的情况。
r($task->updateKanban4BatchCreateTest($taskIdList[2], $executionIdList[2], $laneIdList[9], $columnIdList[2])) && p() && e('0'); // 综合研发界面下看板项目任务泳道为空未开始列的情况。
r($task->updateKanban4BatchCreateTest($taskIdList[2], $executionIdList[2], $laneIdList[9], $columnIdList[2])) && p() && e('0'); // 综合研发界面下看板项目任务泳道列为空的情况。
r($task->updateKanban4BatchCreateTest($taskIdList[5], $executionIdList[3], $laneIdList[1], $columnIdList[2], 'lite')) && p() && e(',6,'); // 运营管理界面下看板项目任务泳道未开始列无卡片的情况。
r($task->updateKanban4BatchCreateTest($taskIdList[5], $executionIdList[3], $laneIdList[3], $columnIdList[3], 'lite')) && p() && e(',6,7,8,807,'); // 运营管理界面下看板项目任务泳道未开始列有卡片的情况。

Some files were not shown because too many files have changed in this diff Show More