Merge branch 'master' of git.zcorp.cc:easycorp/zentaopms

This commit is contained in:
dingguodong
2023-04-27 19:52:31 +08:00
48 changed files with 1938 additions and 886 deletions
+3 -12
View File
@@ -1,13 +1,4 @@
UPDATE `zt_block` SET `module` = 'scrumProject' WHERE `module` = 'project' and type = 'scrum';
UPDATE `zt_block` SET `module` = 'kanbanProject' WHERE `module` = 'project' and type = 'kanban';
UPDATE `zt_block` SET `module` = 'waterfallProject' WHERE `module` = 'project' and type = 'waterfall';
DROP INDEX account_vision_module_type_order ON `zt_block`;
CREATE UNIQUE INDEX `account_vision_module_order` ON `zt_block`(`account`,`vision`,`module`,`order`);
ALTER TABLE `zt_block` ADD `dashboard` varchar(20) NOT NULL DEFAULT '' AFTER `account`;
ALTER TABLE `zt_block` CHANGE `module` `dashboard` varchar(20) NOT NULL DEFAULT '' AFTER `account`;
ALTER TABLE `zt_block` DROP `type`;
ALTER TABLE `zt_block` DROP `source`;
ALTER TABLE `zt_block` CHANGE `block` `code` varchar(30) NOT NULL DEFAULT '' AFTER `dashboard`;
ALTER TABLE `zt_block` MODIFY `vision` varchar(10) NOT NULL DEFAULT 'rnd' AFTER `hidden`;
ALTER TABLE `zt_todo` CHANGE `idvalue` `objectID` mediumint(8) unsigned default '0' NOT NULL AFTER `type`;
UPDATE `zt_block` SET `dashboard` = `module`;
ALTER TABLE `zt_todo` CHANGE `idvalue` `objectID` mediumint(8) unsigned default '0' NOT NULL AFTER `type`;
+14 -2
View File
@@ -927,7 +927,12 @@ class baseControl
}
if(empty($this->output)) $this->parse($moduleName, $methodName);
echo $this->output;
$trace = '';
if($this->config->debug && $this->config->debug >= 2)
{
$trace = $this->app->loadClass('trace')->output();
}
echo $this->output . $trace;
}
/**
@@ -970,7 +975,7 @@ class baseControl
$css = $this->getCSS($moduleName, $methodName, '.ui');
$js = $this->getJS($moduleName, $methodName, '.ui');
if($css) $this->view->pageCSS = $css;
if($js) $this->view->pageJS = $js;
if($js) $this->view->pageJS = $js;
/**
* 切换到视图文件所在的目录,以保证视图文件里面的include语句能够正常运行。
@@ -984,6 +989,13 @@ class baseControl
*/
\zin\zin::$data = (array)$this->view;
\zin\zin::$data['zinDebug'] = array();
if($this->config->debug && $this->config->debug >= 2)
{
\zin\zin::$data['zinDebug']['trace'] = $this->app->loadClass('trace')->getTrace();
}
/**
* 使用extract安定ob方法渲染$viewFile里面的代码。
* Use extract and ob functions to eval the codes in $viewFile.
+113 -4
View File
@@ -356,6 +356,14 @@ class baseRouter
*/
public $siteCode;
/**
* 请求开始时间。
* The start time of the request.
*
* @var float
*/
public $startTime;
/**
* 构造方法, 设置路径,类,超级变量等。注意:
* 1.应该使用createApp()方法实例化router类;
@@ -408,6 +416,9 @@ class baseRouter
if($this->config->framework->autoConnectDB) $this->connectDB();
if($this->config->framework->multiLanguage) $this->setClientLang();
$this->setupProfiling();
$this->setupXhprof();
$this->setEdition();
$this->setVision();
@@ -435,6 +446,19 @@ class baseRouter
return new $className($appName, $appRoot);
}
/**
* 设置请求开始时间。
* The start time of the request.
*
* @param float $startTime
* @access public
* @return void
*/
public function setStartTime(float $startTime)
{
$this->startTime = $startTime;
}
//-------------------- 路径相关方法(Path related methods)--------------------//
/**
@@ -700,6 +724,83 @@ class baseRouter
if(!empty($this->config->debug)) error_reporting(E_ALL & ~ E_STRICT);
}
/**
* 配置数据库性能采样。
* Setup database profiling.
*
* @access protected
* @return void
*/
protected function setupProfiling(): void
{
if(!empty($this->config->debug) && $this->config->debug >= 3) $this->dbh->exec('SET profiling = 1');
}
/**
* 输出数据库性能采样结果(Server-Timing)。
* Output database profiling(Server-Timing).
*
* @access protected
* @return void
*/
protected function outputProfiling(): void
{
if(empty($this->config->debug) || $this->config->debug < 3) return;
/* MySQL profiling. */
$profiling = $this->dbh->query('SHOW PROFILES')->fetchAll(PDO::FETCH_ASSOC);
foreach($profiling as $prof)
{
header('Server-Timing: db;desc="SQL: ' . $prof['Query'] . '";dur=' . $prof['Duration'] * 1000, false);
}
header('Server-Timing: app;desc="PHP: Total";dur=' . (getTime() - $this->startTime) * 1000, false);
}
/**
* 启用Xhprof。
* Setup xhprof.
*
* @return void
*/
protected function setupXhprof(): void
{
if(!empty($this->config->debug) && $this->config->debug >= 4 && extension_loaded('xhprof')) xhprof_enable();
}
/**
* 输出Xhprof结果。
* Output xhprof.
*
* @return bool
*/
protected function outputXhprof(): bool
{
if(empty($this->config->debug) || $this->config->debug < 4 || !extension_loaded('xhprof')) return false;
$log = xhprof_disable();
$xhprofPath = $this->getTmpRoot() . 'xhprof';
$libUtilsPath = $xhprofPath . DS . 'xhprof_lib' . DS . 'utils' . DS;
$outputDir = ini_get('xhprof.output_dir');
if(!is_dir($xhprofPath)) return false;
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);
$runID = $xhprofRuns->save_run($log, "{$this->moduleName}_{$this->methodName}");
header("Xhprof-RunID: {$runID}");
return true;
}
/**
* 设置版本。
* Set edition.
@@ -1926,7 +2027,7 @@ class baseRouter
/* 将扩展文件的代码合并到代码中。Cycle all the extension files and merge them into target lines. */
$extTargets = array();
foreach($extFiles as $extFile) $extTargets[basename((string) $extFile)] = $extFile;
foreach($extTargets as $extTarget) $targetLines .= self::removePHPTAG($extTarget);
foreach($extTargets as $extTarget) $targetLines .= static::removePHPTAG($extTarget);
/* 做个标记,方便后面替换代码使用。Make a mark for replacing codes. */
$replaceMark = '//**//';
@@ -1982,7 +2083,7 @@ class baseRouter
/* 通过文件名获得其对应的方法名。Get methods according it's filename. */
$fileName = baseName((string) $hookFile);
[$method] = explode('.', $fileName);
$hookCodes[$method][] = self::removePHPTAG($hookFile);
$hookCodes[$method][] = static::removePHPTAG($hookFile);
}
/* 合并Hook文件。Cycle the hook methods and merge hook codes. */
@@ -2059,7 +2160,7 @@ class baseRouter
break;
}
}
if(empty($url)) return false;
if(empty($url)) return '';
return $url;
}
@@ -2212,13 +2313,20 @@ class baseRouter
public function loadModule()
{
try {
if(is_null($this->params) and !$this->setParams()) return false;
if(is_null($this->params) and !$this->setParams())
{
$this->outputProfiling();
$this->outputXhprof();
return false;
}
/* 调用该方法 Call the method. */
$module = $this->control;
call_user_func_array(array($module, $this->methodName), $this->params);
$this->checkAPIFile();
$this->outputProfiling();
$this->outputXhprof();
return $module;
} catch (EndResponseException $endResponseException) {
echo $endResponseException->getContent();
@@ -2714,6 +2822,7 @@ class baseRouter
*
* @param object $params the database params.
* @access public
* @return object|bool
*/
public function connectByPDO(object $params): object|bool
{
+6 -3
View File
@@ -589,13 +589,16 @@ class baseDAO
*
* @param string $sql
* @access public
* @return void
* @return array|void
*/
public function explain($sql = '')
public function explain($sql = '', $exit = true)
{
$sql = empty($sql) ? $this->processSQL() : $sql;
$result = $this->dbh->rawQuery('explain ' . $sql)->fetch();
a($result);
if($exit) a($result);
return (array)$result;
}
/**
+144
View File
@@ -0,0 +1,144 @@
<?php
class trace
{
protected $types = array(
'Request' => '请求',
'Files' => '文件',
'SQL Query' => 'SQL 查询',
'SQL Explain' => 'SQL Explain',
);
public $trace = array();
protected $app;
protected $dao;
public function __construct()
{
global $app, $dao;
$this->app = $app;
$this->dao = $dao;
}
public function getRequestInfo()
{
$this->trace['Request'] = array(
'start' => date('Y-m-d H:i:s', (int)$this->app->startTime),
'url' => $this->app->getURI(true),
'protocol' => $this->app->server->server_protocol,
'method' => $this->app->server->request_method,
'timeUsed' => round(getTime() - $this->app->startTime, 4) * 1000,
'memory' => round(memory_get_peak_usage() / 1024, 1),
'querys' => count(dao::$querys),
'caches' => count(dao::$cache),
'files' => count(get_included_files()),
'session' => session_id()
);
}
public function getRequestFiles()
{
$this->trace['Files'] = get_included_files();
}
public function getRequestSqls()
{
$explain = array();
/**
foreach(dao::$querys as $query)
{
$explain[] = $this->dao->explain($query, false);
}
*/
$this->trace['SQL Query'] = dao::$querys;
$this->trace['SQL Explain'] = $explain;
}
public function getTrace()
{
$this->getRequestInfo();
$this->getRequestFiles();
$this->getRequestSqls();
return $this->trace;
}
public function output()
{
$this->getTrace();
$lines = '';
foreach($this->trace as $type => $content)
{
if($type == 'SQL Explain') continue;
$lines .= $this->console($type, empty($content) ? array() : $content);
}
$lines .= $this->printSQLProfile();
$js = <<<JS
<script type='text/javascript'>
{$lines}
</script>
JS;
return $js;
}
protected function console(string $type, $content)
{
$traceTabs = array_keys($this->types);
$line = array();
$line[] = $type == $traceTabs[0] ? "console.group('{$type}');" : "console.groupCollapsed('{$type}');";
foreach((array) $content as $key => $item)
{
switch ($type) {
case 'SQL Query':
$msg = str_replace("\n", '\n', addslashes($item));
$style = "color:#009bb4;";
$line[] = "console.log(\"%c{$msg}\", \"{$style}\");";
$explain = array();
if(!empty($this->trace['SQL Explain']))
{
foreach($this->trace['SQL Explain'][$key] as $explainKey => $explainItem)
{
$explain[] = $explainKey . ': ' . $explainItem;
}
}
$msg = implode(', ', $explain);
$style = "color:red;";
$line[] = "console.log(\"%c{$msg}\", \"{$style}\");";
break;
default:
$item = is_string($key) ? $key . ' ' . $item : $key + 1 . ' ' . $item;
$msg = json_encode($item);
$line[] = "console.log({$msg});";
break;
}
}
$line[] = "console.groupEnd();";
return implode(PHP_EOL, $line);
}
protected function printSQLProfile()
{
$lines = array();
$profiling = $this->dao->dbh->query('SHOW PROFILES')->fetchAll(PDO::FETCH_ASSOC);
if(empty($profiling)) return '';
$lines[] = 'console.groupCollapsed("SQL Profile")';
$lines[] = 'console.table(' . json_encode($profiling) . ')';
$lines[] = 'console.groupEnd()';
return implode(PHP_EOL, $lines);
}
public function __toString(): string
{
return json_encode($this->getTrace());
}
}
+9 -5
View File
@@ -47,12 +47,16 @@ class block extends control
$this->block->create($formData);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => 1));
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => true, 'closeModal' => true, 'callback' => 'loadCurrentPage()'));
}
$this->view->title = $this->lang->block->createBlock;
$this->view->block = $block;
$this->blockZen->buildCreateForm($dashboard, $module, $block);
$this->view->title = $this->lang->block->createBlock;
$this->view->dashboard = $dashboard;
$this->view->block = $block;
$this->view->modules = $this->blockZen->getAvailableModules($dashboard);
$this->view->blocks = $this->blockZen->getAvailableBlocks($dashboard, $module);
$this->view->params = $this->blockZen->getAvailableParams($dashboard, $module, $block);
$this->display();
}
@@ -376,7 +380,7 @@ class block extends control
*/
public function printBlock($id, $module = 'my')
{
$block = $this->block->getByID($id);
$block = $this->block->getByID((int)$id);
if(empty($block)) return false;
+3 -2
View File
@@ -1,7 +1,8 @@
function getForm()
function getForm(event)
{
const field = $(event.target).attr('id');
const module = $('#module').val();
const block = $('#block').val();
const block = field == 'module' ? '' : $('#block').val();
const url = $.createLink('block', 'create', 'dashboard='+ dashboard +'&module=' + module + '&block=' + (block ? block : ''));
loadPage(url, '#blockRow, #paramsRow');
}
+1 -1
View File
@@ -22,7 +22,7 @@ function initData()
/**
title=14:11:23 ERROR: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry &
title=测试 block 模块 model下的 create 方法
timeout=0
cid=39
@@ -18,4 +18,4 @@ global $config;
$block = new blockTest();
$data = $block->getClosedBlockPairsTest('');
r($data) && p('massage') && e('未获取到关闭的区域'); //测试获取关闭的区块键值对
r($data) && p('massage') && e('未获取到关闭的区域'); //测试获取关闭的区块键值对
@@ -0,0 +1,36 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/block.class.php';
su('admin');
function initData()
{
$block = zdTable('block');
$block->id->range('2-3');
$block->account->range('admin');
$block->vision->range('rnd');
$block->module->range('test,my');
$block->title->prefix('区块')->range('3,2');
$block->hidden->range('0-1');
$block->order->range('3-2');
$block->dashboard->range('test,my');
$block->gen(2);
}
/**
title=测试 block 模块 model下的 getMyDashboard 方法
timeout=0
cid=39
*/
global $tester;
$tester->loadModel('block');
initData();
r($tester->block->getMyDashboard('test')) && p('2:account,title') && e('admin,区块3');
r($tester->block->getMyDashboard('my', 1)) && p('3:account,title') && e('admin,区块2');
+30 -24
View File
@@ -1,12 +1,20 @@
<?php
declare(strict_types=1);
/**
* The ui 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 http://www.zentao.net
*/
namespace zin;
set::title($title);
jsVar('dashboard', $dashboard);
$paramsRows = array();
$param = $params['type'];
foreach($params as $code => $row)
{
@@ -21,28 +29,6 @@ foreach($params as $code => $row)
'items' => isset($row['options']) ? $row['options'] : null
))
);
if($code == 'type')
{
$paramsRows[] = formGroup
(
set::label($lang->block->name),
set::name('title'),
set::class('form-row'),
set::control('input')
);
$paramsRows[] = formGroup
(
set::label($lang->block->grid),
set::name("grid"),
set::class('form-row'),
set::control(array
(
'type' => 'select',
'items' => $lang->block->gridOptions
))
);
}
}
form
@@ -80,7 +66,27 @@ form
(
set::id('paramsRow'),
set::class('form-grid'),
$paramsRows
$paramsRows,
$block
? formGroup
(
set::label($lang->block->name),
set::name('title'),
set::class('form-row'),
set::control('input')
) : null,
$block
? formGroup
(
set::label($lang->block->grid),
set::name("grid"),
set::class('form-row'),
set::control(array
(
'type' => 'select',
'items' => $lang->block->gridOptions
))
) : null,
)
);
+32 -60
View File
@@ -2,40 +2,17 @@
class blockZen extends block
{
/**
* Build a form for create block page.
* 构造新增区块页面的表单
* Get module options when adding or editing blocks.
* 添加或编辑区块时获取模块选项
*
* @param string $module
* @param string $dashboard
* @access protected
* @return void
* @return string[]
*/
protected function buildCreateForm(string $dashboard, string $module, string $block)
protected function getAvailableModules(string $dashboard): array
{
$this->buildCreateAndEditForm($dashboard, $module, $block);
$this->view->title = $this->lang->block->createBlock;
}
if($dashboard != 'my') return array();
protected function buildEditForm(int $blockID, string $dashboard)
{
$this->buildCreateAndEditForm($dashboard);
$this->view->title = $this->lang->block->editBlock;
$this->view->block = $this->block->getByID($blockID);
}
private function buildCreateAndEditForm($dashboard, $module, $block)
{
if($dashboard == 'my')
{
return $this->buildCreateAndEditFormByTerritory($dashboard, $module, $block);
}
else
{
return $this->buildCreateAndEditFormByModule($dashboard);
}
}
private function buildCreateAndEditFormByTerritory($dashboard, $module, $block)
{
$modules = $this->lang->block->moduleList;
unset($modules['doc']);
@@ -69,37 +46,21 @@ class blockZen extends block
$hiddenBlocks = $this->block->getMyHiddenBlocks('my');
foreach($hiddenBlocks as $block) $modules['hiddenBlock' . $block->id] = $block->title;
$this->view->modules = $modules;
$this->view->blocks = $this->getAvailableBlocks($dashboard);
$this->view->params = $this->getAvailableParams($dashboard, $module, $block);
$this->view->dashboard = $dashboard;
$this->view->module = '';
return $modules;
}
private function buildCreateAndEditFormByModule($dashboard)
/**
* Get block options when adding or editing blocks.
* 添加或编辑区块时获取区块选项
*
* @param string $dashboard
* @param string $module
* @access protected
* @return string[]|true
*/
protected function getAvailableBlocks($dashboard, $module): array|bool
{
if($this->config->edition == 'max' and strpos($dashboard, 'Project') !== false)
{
if($dashboard == 'scrumProject')
{
if(!helper::hasFeature("scrum_issue")) unset($this->lang->block->modules['scrum']['index']->availableBlocks->scrumissue);
if(!helper::hasFeature("scrum_risk")) unset($this->lang->block->modules['scrum']['index']->availableBlocks->scrumrisk);
}
if($dashboard == 'waterfallProject')
{
if(!helper::hasFeature("waterfall_issue")) unset($this->lang->block->modules['waterfall']['index']->availableBlocks->waterfallissue);
if(!helper::hasFeature("waterfall_risk")) unset($this->lang->block->modules['waterfall']['index']->availableBlocks->waterfallrisk);
}
}
$this->view->blocks = $this->getAvailableBlocks($dashboard);
$this->view->dashboard = $dashboard;
$this->view->module = $dashboard;
}
private function getAvailableBlocks($dashboard)
{
$module = $this->get->module;
$blocks = $this->block->getAvailableBlocks($dashboard, $module);
if(!$this->selfCall)
@@ -111,13 +72,24 @@ class blockZen extends block
return !empty($blocks) ? $blocks : array();
}
private function getAvailableParams(string $dashboard, string $module = '', string $block = '') : array
/**
* Get other form items when adding or editing blocks
* 添加或编辑区块时获取其他表单项
*
* @param string $dashboard
* @param string $module
* @param string $block
* @access protected
* @return array[]
*/
protected function getAvailableParams(string $dashboard, string $module = '', string $block = ''): array
{
if(!isset($this->lang->block->moduleList[$module])) return array();
if(!$block) return array();
$params = $this->block->getParams($module, $module);
return !empty($params) ? json_decode($params, true) : array();
$params = json_decode($this->block->getParams($block, $module), true);
return !empty($params) ? $params : array();
}
}
+18 -2
View File
@@ -1,6 +1,22 @@
<?php
$config->project->form = new stdclass();
$config->project->form->start = array();
$config->project->form->suspend = array();
$config->project->form->create = array();
$config->project->form->start = array();
$config->project->form->create['parent'] = array('type' => 'int', 'required' => false, 'default' => '');
$config->project->form->create['name'] = array('type' => 'string', 'required' => true, 'filter' => 'trim');
$config->project->form->create['code'] = array('type' => 'string', 'required' => true, 'filter' => 'trim');
$config->project->form->create['multiple'] = array('type' => 'string', 'required' => false, 'default' => '');
$config->project->form->create['hasProduct'] = array('type' => 'string', 'required' => false, 'default' => '');
$config->project->form->create['PM'] = array('type' => 'string', 'required' => false, 'default' => '');
$config->project->form->create['budget'] = array('type' => 'string', 'required' => false, 'default' => '');
$config->project->form->create['budgetUnit'] = array('type' => 'string', 'required' => false, 'default' => 'CNY');
$config->project->form->create['begin'] = array('type' => 'date', 'required' => true);
$config->project->form->create['end'] = array('type' => 'date', 'required' => true);
$config->project->form->create['desc'] = array('type' => 'string', 'required' => false, 'default' => '');
$config->project->form->create['acl'] = array('type' => 'string', 'required' => false, 'default' => '');
$config->project->form->create['whitelist'] = array('type' => 'array', 'required' => false, 'default' => '');
$config->project->form->create['auth'] = array('type' => 'array', 'required' => false, 'default' => '');
$config->project->form->create['model'] = array('type' => 'string', 'required' => false, 'default' => '');
$config->project->form->start['realBegan'] = array('type' => 'date', 'required' => true, 'filter' => 'trim');
+17 -38
View File
@@ -277,7 +277,7 @@ class project extends control
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('tminBegin');
$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');
}
}
@@ -487,48 +487,25 @@ class project extends control
$this->loadModel('execution');
$this->loadModel('product');
$this->session->set('projectModel', $model);
if($model == 'kanban') unset($this->lang->project->authList['reset']);
if($_POST)
{
$projectID = $this->project->create();
$postData = form::data($this->config->project->form->create);
$project = $this->projectZen->prepareCreateExtras($postData);
$projectID = $this->project->create($project, $postData);
$projectID = (int)$projectID;
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$this->loadModel('action')->create('project', $projectID, 'opened');
/* Link the plan stories. */
if(!empty($_POST['hasProduct']) && !empty($_POST['plans']))
{
$planIdList = array();
foreach($_POST['plans'] as $plans)
{
foreach($plans as $planID)
{
$planIdList[$planID] = $planID;
}
}
$planStoryGroup = $this->loadModel('story')->getStoriesByPlanIdList($planIdList);
foreach($planIdList as $planID)
{
$planStories = $planProducts = array();
$planStory = isset($planStoryGroup[$planID]) ? $planStoryGroup[$planID] : array();
if(!empty($planStory))
{
foreach($planStory as $id => $story)
{
if($story->status == 'draft' or $story->status == 'reviewing')
{
unset($planStory[$id]);
continue;
}
$planProducts[$story->id] = $story->product;
}
$planStories = array_keys($planStory);
$this->execution->linkStory($projectID, $planStories, $planProducts);
}
}
}
if(!empty($_POST['hasProduct']) && !empty($_POST['plans'])) $this->projectZen->linkPlanStories($postData);
$message = $this->executeHooks($projectID);
if($message) $this->lang->saveSuccess = $message;
@@ -551,7 +528,7 @@ class project extends control
$parent = isset($_POST['parent']) ? $_POST['parent'] : 0;
$systemMode = $this->loadModel('setting')->getItem('owner=system&module=common&section=global&key=mode');
if(!empty($systemMode) and $systemMode == 'light') $parent = 0;
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('project', 'browse', "programID=$parent&browseType=all", '', '', $projectID)));
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('project', 'browse', "programID=$parent&browseType=all", '', false, $projectID)));
}
}
@@ -1539,8 +1516,9 @@ class project extends control
* @access public
* @return void
*/
public function team($projectID = 0)
public function team(string $projectID = '0')
{
$projectID = (int)$projectID;
$this->session->set('teamList', $this->app->getURI(true), 'project');
$this->app->loadLang('execution');
@@ -1809,7 +1787,6 @@ class project extends control
$postData = $this->projectZen->prepareSuspendExtras($projectID, $postData);
print_r($postData);die;
$changes = $this->project->suspend($projectID, $postData);
if(dao::isError()) return print(js::error(dao::getError()));
@@ -2046,8 +2023,10 @@ class project extends control
* @access public
* @return void
*/
public function manageProducts($projectID, $from = 'project')
public function manageProducts(string $projectID, $from = 'project')
{
$projectID = (int)$projectID;
$this->loadModel('product');
$this->loadModel('program');
$this->loadModel('execution');
+7 -5
View File
@@ -64,8 +64,6 @@ $lang->project->daysGreaterProject = "Days cannot be greater than days of {$lan
$lang->project->errorHours = 'Hours/Day cannot be greater than『24』';
$lang->project->workdaysExceed = 'No more than『%s』working days';
$lang->project->teamMembersCount = ', there are %s team members.';
$lang->project->budgetNumber = '『Budget』must be numbers.';
$lang->project->budgetGe0 = '『Budget』must be greater than or equal to 0.';
$lang->project->allProjects = "All {$lang->projectCommon}s";
$lang->project->ignore = 'Ignore';
$lang->project->disableExecution = "{$lang->projectCommon} of disable {$lang->executionCommon}";
@@ -211,8 +209,6 @@ $lang->project->noProgram = "Independent {$lang->projectCommon}s";
$lang->project->laneColorList = array('#32C5FF', '#006AF1', '#9D28B2', '#FF8F26', '#FFC20E', '#00A78E', '#7FBB00', '#424BAC', '#C0E9FF', '#EC2761');
$lang->project->productNotEmpty = "Please link {$lang->productCommon}s or create {$lang->productCommon}s.";
$lang->project->existProductName = "{$lang->productCommon} name already exists.";
$lang->project->changeProgram = "%s > Change {$lang->projectCommon}";
$lang->project->changeProgramTip = "Once the program is edited, the {$lang->productCommon} that is linked to this program will be changed. Do you want to edit it?";
$lang->project->linkedProjectsTip = "Linked {$lang->projectCommon}s are as follows";
@@ -242,6 +238,13 @@ $lang->project->tip->actived = 'The project has been activated. Re-activated
$lang->project->tip->group = "It's a Kanban project. Editing privilege group is not available.";
$lang->project->tip->whitelist = "It's a public project with open permissions. No need to edit whitelists.";
$lang->project->error = new stdclass();
$lang->project->error->existProductName = "{$lang->productCommon} name already exists.";
$lang->project->error->budgetGe0 = '『Budget』must be greater than or equal to 0.';
$lang->project->error->budgetNumber = '『Budget』must be numbers.';
$lang->project->error->productNotEmpty = "Please link {$lang->productCommon}s or create {$lang->productCommon}s.";
$lang->project->error->emptyBranch = 'Branch can not be empty!';
$lang->project->hundredMillion = 'Hundred Million';
$lang->project->unitList['CNY'] = 'RMB';
@@ -385,7 +388,6 @@ $lang->project->agileplus = 'Agile +';
$lang->project->waterfallplus = 'Waterfall +';
$lang->project->cannotCreateChild = 'It is not empty, so you cannot add a child. You can add a parent for it, and then create a child.';
$lang->project->emptyPM = 'No manager';
$lang->project->emptyBranch = 'Branch can not be empty!';
$lang->project->cannotChangeToCat = "It is not empty, so you cannot change it to a parent.";
$lang->project->cannotCancelCat = "It has child {$lang->projectCommon}s, so you cannot unmark the parent.";
$lang->project->parentBeginEnd = "Parent begin&end date: %s ~ %s";
+7 -5
View File
@@ -64,8 +64,6 @@ $lang->project->daysGreaterProject = "Days cannot be greater than days of {$lan
$lang->project->errorHours = 'Hours/Day cannot be greater than『24』';
$lang->project->workdaysExceed = 'No more than『%s』working days';
$lang->project->teamMembersCount = ', there are %s team members.';
$lang->project->budgetNumber = '『Budget』must be numbers.';
$lang->project->budgetGe0 = '『Budget』must be greater than or equal to 0.';
$lang->project->allProjects = "All {$lang->projectCommon}s";
$lang->project->ignore = 'Ignore';
$lang->project->disableExecution = "{$lang->projectCommon} of disable {$lang->executionCommon}";
@@ -213,8 +211,6 @@ $lang->project->noProgram = "Independent {$lang->projectCommon}s";
$lang->project->laneColorList = array('#32C5FF', '#006AF1', '#9D28B2', '#FF8F26', '#FFC20E', '#00A78E', '#7FBB00', '#424BAC', '#C0E9FF', '#EC2761');
$lang->project->productNotEmpty = "Please link {$lang->productCommon}s or create {$lang->productCommon}s.";
$lang->project->existProductName = "{$lang->productCommon} name already exists.";
$lang->project->changeProgram = "%s > Change {$lang->projectCommon}";
$lang->project->changeProgramTip = "Once the program is edited, the {$lang->productCommon} that is linked to this program will be changed. Do you want to edit it?";
$lang->project->linkedProjectsTip = "Linked {$lang->projectCommon}s are as follows";
@@ -243,6 +239,13 @@ $lang->project->tip->actived = 'The project has been activated. Re-activated
$lang->project->tip->group = "It's a Kanban project. Editing privilege group is not available.";
$lang->project->tip->whitelist = "It's a public project with open permissions. No need to edit whitelists.";
$lang->project->error = new stdclass();
$lang->project->error->existProductName = "{$lang->productCommon} name already exists.";
$lang->project->error->budgetGe0 = '『Budget』must be greater than or equal to 0.';
$lang->project->error->budgetNumber = '『Budget』must be numbers.';
$lang->project->error->productNotEmpty = "Please link {$lang->productCommon}s or create {$lang->productCommon}s.";
$lang->project->error->emptyBranch = 'Branch can not be empty!';
$lang->project->tenThousand = 'Ten Thousand';
$lang->project->hundredMillion = 'Hundred Million';
@@ -387,7 +390,6 @@ $lang->project->agileplus = 'Agile +';
$lang->project->waterfallplus = 'Waterfall +';
$lang->project->cannotCreateChild = 'It is not empty, so you cannot add a child. You can add a parent for it, and then create a child.';
$lang->project->emptyPM = 'No manager';
$lang->project->emptyBranch = 'Branch can not be empty!';
$lang->project->cannotChangeToCat = "It is not empty, so you cannot change it to a parent.";
$lang->project->cannotCancelCat = "It has child {$lang->projectCommon}s, so you cannot unmark the parent.";
$lang->project->parentBeginEnd = "Parent begin&end date: %s ~ %s";
+7 -5
View File
@@ -64,8 +64,6 @@ $lang->project->daysGreaterProject = "Days cannot be greater than days of {$lan
$lang->project->errorHours = 'Hours/Day cannot be greater than『24』';
$lang->project->workdaysExceed = 'No more than『%s』working days';
$lang->project->teamMembersCount = ', there are %s team members.';
$lang->project->budgetNumber = '『Budget』must be numbers.';
$lang->project->budgetGe0 = '『Budget』must be greater than or equal to 0.';
$lang->project->allProjects = "All {$lang->projectCommon}s";
$lang->project->ignore = 'Ignore';
$lang->project->disableExecution = "{$lang->projectCommon} of disable {$lang->executionCommon}";
@@ -212,8 +210,6 @@ $lang->project->noProgram = "Independent {$lang->projectCommon}s";
$lang->project->laneColorList = array('#32C5FF', '#006AF1', '#9D28B2', '#FF8F26', '#FFC20E', '#00A78E', '#7FBB00', '#424BAC', '#C0E9FF', '#EC2761');
$lang->project->productNotEmpty = "Please link {$lang->productCommon}s or create {$lang->productCommon}s.";
$lang->project->existProductName = "{$lang->productCommon} name already exists.";
$lang->project->changeProgram = "%s > Change {$lang->projectCommon}";
$lang->project->changeProgramTip = "Once the program is edited, the {$lang->productCommon} that is linked to this program will be changed. Do you want to edit it?";
$lang->project->linkedProjectsTip = "Linked {$lang->projectCommon}s are as follows";
@@ -243,6 +239,13 @@ $lang->project->tip->actived = 'The project has been activated. Re-activated
$lang->project->tip->group = "It's a Kanban project. Editing privilege group is not available.";
$lang->project->tip->whitelist = "It's a public project with open permissions. No need to edit whitelists.";
$lang->project->error = new stdclass();
$lang->project->error->existProductName = "{$lang->productCommon} name already exists.";
$lang->project->error->budgetGe0 = '『Budget』must be greater than or equal to 0.';
$lang->project->error->budgetNumber = '『Budget』must be numbers.';
$lang->project->error->productNotEmpty = "Please link {$lang->productCommon}s or create {$lang->productCommon}s.";
$lang->project->error->emptyBranch = 'Branch can not be empty!';
$lang->project->hundredMillion = 'Hundred Million';
$lang->project->unitList['CNY'] = 'RMB';
@@ -385,7 +388,6 @@ $lang->project->agileplus = 'Agile +';
$lang->project->waterfallplus = 'Waterfall +';
$lang->project->cannotCreateChild = 'It is not empty, so you cannot add a child. You can add a parent for it, and then create a child.';
$lang->project->emptyPM = 'No manager';
$lang->project->emptyBranch = 'Branch can not be empty!';
$lang->project->cannotChangeToCat = "It is not empty, so you cannot change it to a parent.";
$lang->project->cannotCancelCat = "It has child {$lang->projectCommon}s, so you cannot unmark the parent.";
$lang->project->parentBeginEnd = "Parent begin&end date: %s ~ %s";
+7 -5
View File
@@ -64,8 +64,6 @@ $lang->project->daysGreaterProject = "可用工日不能大于{$lang->projectCo
$lang->project->errorHours = '可用工时/天不能大于『24』';
$lang->project->workdaysExceed = '可用工作日不能超过『%s』天';
$lang->project->teamMembersCount = ',团队成员共%s人。';
$lang->project->budgetNumber = '『预算』金额必须为数字。';
$lang->project->budgetGe0 = '『预算』金额必须大于等于0。';
$lang->project->allProjects = "所有{$lang->projectCommon}";
$lang->project->ignore = '忽略';
$lang->project->disableExecution = "不启用{$lang->executionCommon}的{$lang->projectCommon}";
@@ -212,8 +210,6 @@ $lang->project->noProgram = "无项目集归属{$lang->projectCommon}";
$lang->project->laneColorList = array('#32C5FF', '#006AF1', '#9D28B2', '#FF8F26', '#FFC20E', '#00A78E', '#7FBB00', '#424BAC', '#C0E9FF', '#EC2761');
$lang->project->productNotEmpty = "请关联{$lang->productCommon}或创建{$lang->productCommon}。";
$lang->project->existProductName = "{$lang->productCommon}名称已存在。";
$lang->project->changeProgram = '%s > 修改项目集';
$lang->project->changeProgramTip = "修改项目集后,该{$lang->projectCommon}关联{$lang->productCommon}的项目集也会被修改,请确认是否修改。";
$lang->project->linkedProjectsTip = "关联的{$lang->projectCommon}如下";
@@ -234,6 +230,13 @@ $lang->project->allSummary = "本页共 <strong>%s</strong> 个{$lan
$lang->project->checkedSummary = "选中 <strong>%total%</strong> 个{$lang->projectCommon}。";
$lang->project->checkedAllSummary = "选中 <strong>%total%</strong> 个{$lang->projectCommon},未开始 <strong>%wait%</strong>,进行中 <strong>%doing%</strong>,已挂起 <strong>%suspended%</strong>,已关闭 <strong>%closed%</strong> 。";
$lang->project->error = new stdclass();
$lang->project->error->existProductName = "{$lang->productCommon}名称已存在。";
$lang->project->error->budgetGe0 = '『预算』金额必须大于等于0。';
$lang->project->error->budgetNumber = '『预算』金额必须为数字。';
$lang->project->error->productNotEmpty = "请关联{$lang->productCommon}或创建{$lang->productCommon}。";
$lang->project->error->emptyBranch = '分支不能为空!';
$lang->project->tip = new stdclass();
$lang->project->tip->closed = '该项目已是关闭状态,无须关闭。';
$lang->project->tip->notSuspend = '该项目已关闭,不可进行挂起操作。';
@@ -386,7 +389,6 @@ $lang->project->agileplus = '融合敏捷';
$lang->project->waterfallplus = '融合瀑布';
$lang->project->cannotCreateChild = "该{$lang->projectCommon}已经有实际的内容,无法直接添加子{$lang->projectCommon}。您可以为当前{$lang->projectCommon}创建一个父{$lang->projectCommon},然后在新的父{$lang->projectCommon}下面添加子{$lang->projectCommon}。";
$lang->project->emptyPM = '暂无';
$lang->project->emptyBranch = '分支不能为空!';
$lang->project->cannotChangeToCat = "该{$lang->projectCommon}已经有实际的内容,无法修改为父{$lang->projectCommon}";
$lang->project->cannotCancelCat = "该{$lang->projectCommon}下已经有子{$lang->projectCommon},无法取消父{$lang->projectCommon}标记";
$lang->project->parentBeginEnd = "父{$lang->projectCommon}起止时间:%s ~ %s";
+18 -157
View File
@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
class projectModel extends model
{
/**
@@ -179,24 +180,19 @@ class projectModel extends model
/**
* Get a project by id.
* 根据项目ID获取项目信息。
*
* @param int $projectID
* @param string $type project|sprint,stage
* @access public
* @return object
* @return object|false
*/
public function getByID($projectID, $type = 'project')
public function getByID(int $projectID): object|false
{
if(defined('TUTORIAL')) return $this->loadModel('tutorial')->getProject();
$project = $this->dao->select('*')->from(TABLE_PROJECT)
->where('id')->eq($projectID)
->andWhere('`type`')->in($type)
->fetch();
$project = $this->projectTao->fetchProjectInfo($projectID);
if(!$project) return false;
if(helper::isZeroDate($project->end)) $project->end = '';
$project = $this->loadModel('file')->replaceImgURL($project, 'desc');
return $project;
}
@@ -1049,7 +1045,7 @@ class projectModel extends model
public function buildMenuQuery($projectID = 0)
{
$path = '';
$project = $this->getByID($projectID);
$project = $this->projectTao->fetchProjectInfo($projectID);
if($project) $path = $project->path;
return $this->dao->select('*')->from(TABLE_PROJECT)
@@ -1266,120 +1262,13 @@ class projectModel extends model
* @access public
* @return int|bool
*/
public function create()
public function create(object $project, object $postData)
{
$project = fixer::input('post')
->callFunc('name', 'trim')
->setDefault('status', 'wait')
->setIF($this->post->delta == 999, 'end', LONG_TIME)
->setIF($this->post->delta == 999, 'days', 0)
->setIF($this->post->acl == 'open', 'whitelist', '')
->setIF(!isset($_POST['whitelist']), 'whitelist', '')
->setIF(!isset($_POST['multiple']), 'multiple', '1')
->setDefault('openedBy', $this->app->user->account)
->setDefault('openedDate', helper::now())
->setDefault('team', $this->post->name)
->setDefault('lastEditedBy', $this->app->user->account)
->setDefault('lastEditedDate', helper::now())
->setDefault('days', '0')
->add('type', 'project')
->join('whitelist', ',')
->stripTags($this->config->project->editor->create['id'], $this->config->allowedTags)
->remove('uid,products,branch,plans,delta,newProduct,productName,future,contactListMenu,teamMembers')
->get();
if(!isset($this->config->setCode) or $this->config->setCode == 0) unset($project->code);
/* Lean mode relation defaultProgram. */
if($this->config->systemMode == 'light') $project->parent = $this->config->global->defaultProgram;
$linkedProductsCount = 0;
if($project->hasProduct && isset($_POST['products']))
{
foreach($_POST['products'] as $product)
{
if(!empty($product)) $linkedProductsCount++;
}
}
if($_POST['products'])
{
$topProgramID = $this->loadModel('program')->getTopByID($project->parent);
$multipleProducts = $this->loadModel('product')->getMultiBranchPairs($topProgramID);
foreach($_POST['products'] as $index => $productID)
{
if(isset($multipleProducts[$productID]) and empty($_POST['branch'][$index]))
{
dao::$errors[] = $this->lang->project->emptyBranch;
return false;
}
}
}
$program = new stdClass();
if($project->parent)
{
$program = $this->dao->select('*')->from(TABLE_PROGRAM)->where('id')->eq($project->parent)->fetch();
/* Judge products not empty. */
if($project->hasProduct && empty($linkedProductsCount) and !isset($_POST['newProduct']))
{
dao::$errors['products0'] = $this->lang->project->productNotEmpty;
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;
}
if(!empty($project->budget))
{
if(!is_numeric($project->budget))
{
dao::$errors['budget'] = sprintf($this->lang->project->budgetNumber);
return false;
}
else if(is_numeric($project->budget) and ($project->budget < 0))
{
dao::$errors['budget'] = sprintf($this->lang->project->budgetGe0);
return false;
}
else
{
$project->budget = round((float)$this->post->budget, 2);
}
}
/* When select create new product, product name cannot be empty and duplicate. */
if($project->hasProduct && isset($_POST['newProduct']))
{
if(empty($_POST['productName']))
{
$this->app->loadLang('product');
dao::$errors['productName'] = sprintf($this->lang->error->notempty, $this->lang->product->name);
return false;
}
else
{
$programID = isset($project->parent) ? $project->parent : 0;
$existProductName = $this->dao->select('name')->from(TABLE_PRODUCT)->where('name')->eq($_POST['productName'])->andWhere('program')->eq($programID)->fetch('name');
if(!empty($existProductName))
{
dao::$errors['productName'] = $this->lang->project->existProductName;
return false;
}
}
}
$requiredFields = $this->config->project->create->requiredFields;
if($this->post->delta == 999) $requiredFields = trim(str_replace(',end,', ',', ",{$requiredFields},"), ',');
if($postData->rawdata->delta == 999) $requiredFields = trim(str_replace(',end,', ',', ",{$requiredFields},"), ',');
$this->lang->error->unique = $this->lang->error->repeat;
$project = $this->loadModel('file')->processImgURL($project, $this->config->project->editor->create['id'], $this->post->uid);
$project = $this->loadModel('file')->processImgURL($project, $this->config->project->editor->create['id'], $postData->rawdata->uid);
$this->dao->insert(TABLE_PROJECT)->data($project)
->autoCheck()
->batchcheck($requiredFields, 'notempty')
@@ -1460,9 +1349,9 @@ class projectModel extends model
{
/* If parent not empty, link products or create products. */
$product = new stdclass();
$product->name = $project->hasProduct && $this->post->productName ? $this->post->productName : $project->name;
$product->name = $project->hasProduct && $postData->rawdata->productName ? $postData->rawdata->productName : $project->name;
$product->shadow = zget($project, 'vision', 'rnd') == 'rnd' ? (int)empty($project->hasProduct) : 1;
$product->bind = $this->post->parent ? 0 : 1;
$product->bind = $postData->rawdata->parent ? 0 : 1;
$product->program = $project->parent ? current(array_filter(explode(',', $program->path))) : 0;
$product->acl = $project->acl == 'open' ? 'open' : 'private';
$product->PO = $project->PM;
@@ -1510,7 +1399,7 @@ class projectModel extends model
/* Save order. */
$this->dao->update(TABLE_PROJECT)->set('`order`')->eq($projectID * 5)->where('id')->eq($projectID)->exec();
$this->file->updateObjectID($this->post->uid, $projectID, 'project');
$this->file->updateObjectID($postData->rawdata->uid, $projectID, 'project');
$this->loadModel('program')->setTreePath($projectID);
/* Add project admin. */
@@ -1859,34 +1748,6 @@ class projectModel extends model
return common::createChanges($oldProject, $project);
}
/**
* Put project off.
*
* @param int $projectID
* @access public
* @return void
*/
public function putoff($projectID)
{
$oldProject = $this->getById($projectID);
$now = helper::now();
$project = fixer::input('post')
->add('id', $projectID)
->setDefault('lastEditedBy', $this->app->user->account)
->setDefault('lastEditedDate', $now)
->remove('comment')
->get();
$this->dao->update(TABLE_PROJECT)->data($project)
->autoCheck()
->checkFlow()
->where('id')->eq((int)$projectID)
->exec();
if(!dao::isError()) return common::createChanges($oldProject, $project);
}
/**
* Suspend project and update status.
* 暂停项目并更改其状态
@@ -1924,7 +1785,7 @@ class projectModel extends model
public function activate(int $projectID, object $project) :array|false
{
$now = helper::now();
$oldProject = $this->getByID($projectID);
$oldProject = $this->projectTao->fetchProjectInfo($projectID);
$daoSuccess = $this->projectTao->doActivate($projectID, $project);
if(!$daoSuccess) return false;
@@ -2108,7 +1969,7 @@ class projectModel extends model
*/
public function manageMembers($projectID)
{
$project = $this->getByID($projectID);
$project = $this->projectTao->fetchProjectInfo($projectID);
$data = (array)fixer::input('post')->get();
extract($data);
@@ -2587,7 +2448,7 @@ class projectModel extends model
$this->dao->update(TABLE_EXECUTION)->set('division')->eq('1')->where('project')->eq((int)$projectID)->exec();
}
$project = $this->getByID($projectID);
$project = $this->projectTao->fetchProjectInfo($projectID);
if(!empty($project) and ($project->model == 'waterfall' or $project->model == 'waterfallplus') and empty($project->division) and !empty($executions))
{
$this->loadModel('execution');
@@ -2628,7 +2489,7 @@ class projectModel extends model
{
if(defined('TUTORIAL')) return $this->loadModel('tutorial')->getTeamMembers();
$project = $this->getByID($projectID);
$project = $this->projectTao->fetchProjectInfo($projectID);
if(empty($project)) return array();
return $this->dao->select("t1.*, t1.hours * t1.days AS totalHours, t2.id as userID, if(t2.deleted='0', t2.realname, t1.account) as realname")->from(TABLE_TEAM)->alias('t1')
@@ -2865,14 +2726,14 @@ class projectModel extends model
$model = 'scrum';
$objectID = (empty($objectID) and $this->session->project) ? $this->session->project : $objectID;
$project = $this->getByID($objectID);
$project = $this->projectTao->fetchProjectInfo($objectID);
if(!$project)
{
$execution = $this->loadModel('execution')->getByID($objectID);
if($execution and $execution->project and !$execution->multiple)
{
$project = $this->getByID($execution->project);
$project = $this->projectTao->fetchProjectInfo($execution->project);
$objectID = $execution->project;
}
}
+17
View File
@@ -131,4 +131,21 @@ class projectTao extends projectModel
return true;
}
/**
* Get project details, including all contents of the TABLE_PROJECT.
* 获取项目的详情,包含project表的所有内容。
*
* @param int $projectID
* @access protected
* @return object|false
*/
protected function fetchProjectInfo(int $projectID): object|false
{
$project = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch();
/* Filter the date is empty or 1970. */
if($project and helper::isZeroDate($project->end)) $project->end = '';
return $project;
}
}
+57 -35
View File
@@ -4,55 +4,77 @@ include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/project.class.php';
su('admin');
$program = zdTable('project');
$program->id->range('1');
$program->name->range('项目集一');
$program->model->range('program');
$program->code->range('项目集代号');
$program->desc->range('测试项目集');
$program->gen(1);
/**
title=测试 projectModel->create();
timeout=0
cid=1
pid=1
创建新项目 >> 测试新增项目一
项目名称为空时 >> 『项目名称』不能为空。
项目的完成时间为空 >> 『计划完成』不能为空。
项目的计划完成时间大于计划开始时间 >> 『计划完成』应当大于『2022-02-07』。
项目的开始时间为空 >> 『计划开始』不能为空。
- 执行projectClass模块的cr方法,参数是$normalProject, $postData,属性name @测试新增项目一
- 执行projectClass模块的cr方法,参数是$emptyNameProject, $postData,属性name @『项目名称』不能为空。
- 执行projectClass模块的cr方法,参数是$emptyEndProject, $postData,属性end @『计划完成』不能为空。
- 执行projectClass模块的cr方法,参数是$beginGtEndProject, $postData,属性end @『计划完成』应当大于『2022-02-07』。
- 执行projectClass模块的cr方法,参数是$emptyBeginProject, $postData,属性begin @『计划开始』不能为空。
*/
global $tester;
$tester->app->loadConfig('execution');
$project = new Project();
$projectClass = new project();
$data = array(
'parent' => 1,
'name' => '测试新增项目一',
'budget' => '',
'budgetUnit' => 'CNY',
'begin' => '2022-02-07',
'end' => '2022-03-01',
'desc' => '测试项目描述',
'acl' => 'private',
'whitelist' => '',
'PM' => '',
'products' => array(1)
);
$project = new stdclass();
$project->parent = 0;
$project->name = '测试新增项目一';
$project->budget = '';
$project->budgetUnit = 'CNY';
$project->begin = '2022-02-07';
$project->end = '2023-01-01';
$project->desc = '测试项目描述';
$project->acl = 'private';
$project->whitelist = 'user1,user2,user3';
$project->PM = 'admin';
$project->type = 'project';
$project->model = 'scrum';
$project->multiple = 1;
$project->hasProduct = 1;
$normalProject = $data;
$postData = new stdclass();
$postData->rowdata = clone $project;
$postData->rowdata->uid = '64dda2xc';
$postData->rowdata->delta = 0;
$postData->rowdata->products = array(1);
$emptyNameProject = $data;
$emptyNameProject['name'] = '';
$normalProject = clone $project;
$emptyBeginProject = $data;
$emptyBeginProject['begin'] = '';
$emptyNameProject = clone $project;
$emptyNameProject->name = '';
$emptyEndProject = $data;
$emptyEndProject['end'] = '';
$emptyBeginProject = clone $project;
$emptyBeginProject->name = '测试新增项目二';
$emptyBeginProject->begin = '';
$beginGtEndProject = $data;
$beginGtEndProject['end'] = '2022-01-10';
$emptyEndProject = clone $project;
$emptyEndProject->end = '';
$emptyEndProject->name = '测试新增项目三';
r($project->create($normalProject)) && p('name') && e('测试新增项目一'); // 创建新项目
r($project->create($emptyNameProject)) && p('message[name]:0') && e('『项目名称』不能为空。'); // 项目名称为空时
r($project->create($emptyEndProject)) && p('message[end]:0') && e('『计划完成』不能为空。'); // 项目的完成时间为空
r($project->create($beginGtEndProject)) && p('message[end]:0') && e('『计划完成』应当大于『2022-02-07』。'); // 项目的计划完成时间大于计划开始时间
r($project->create($emptyBeginProject)) && p('message[begin]:0') && e('『计划开始』不能为空。'); // 项目的开始时间为空
$beginGtEndProject = clone $project;
$beginGtEndProject->end = '2021-01-10';
$beginGtEndProject->name = '测试新增项目四';
r($projectClass->create($normalProject, $postData)) && p('name') && e('测试新增项目一');
r($projectClass->create($emptyNameProject, $postData)) && p('message[name]:0') && e('『项目名称』不能为空。');
r($projectClass->create($emptyEndProject, $postData)) && p('message[end]:0') && e('『计划完成』不能为空。');
r($projectClass->create($beginGtEndProject, $postData)) && p('message[end]:0') && e('『计划完成』应当大于『2022-02-07』。');
r($projectClass->create($emptyBeginProject, $postData)) && p('message[begin]:0') && e('『计划开始』不能为空。');
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/project.class.php';
su('admin');
/**
title=测试 projectModel->activate();
cid=1
pid=1
*/
function initData()
{
$project = zdTable('project');
$project->id->range('2-5');
$project->project->range('2-5');
$project->name->prefix("项目")->range('2-5');
$project->code->prefix("project")->range('2-5');
$project->model->range("scrum");
$project->auth->range("[]");
$project->path->range("[]");
$project->type->range("project");
$project->grade->range("1");
$project->days->range("1");
$project->status->range("closed, suspended");
$project->desc->range("[]");
$project->budget->range("100000,200000");
$project->budgetUnit->range("CNY");
$project->percent->range("0-0");
$project->gen(2);
}
initData();
global $tester;
$tester->loadModel('project');
$project = new Project();
$data = new stdClass();
$data->status = 'doing';
$data->begin = '2022-10-10';
$data->end = '2022-10-10';
$data->status = 'doing';
$data->comment = 'fgasgqasfdgasfgasg';
$data->readjustTime = 1;
$data->readjustTask = 1;
r(strlen($tester->project->doActivate(2, $data))) && p() && e(true); // 判断是否更新无报错 true
r(strlen($tester->project->doActivate(3, $data))) && p() && e(true); // 判断是否更新无报错 true
+2 -2
View File
@@ -45,5 +45,5 @@ $tester->loadModel('project');
initData();
r($tester->project->getByID(2, 'project')) && p('code,type') && e('project2,project'); //获取ID等于11的项目
r($tester->project->getByID(1, 'project')) && p('code') && e('0'); //获取不存在的项目
r($tester->project->getByID(2)) && p('code,type') && e('project2,project'); //获取ID等于11的项目
r($tester->project->getByID(1)) && p('code') && e('0'); //获取不存在的项目
-25
View File
@@ -1,25 +0,0 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
su('admin');
/**
title=测试 projectModel->start();
cid=1
pid=1
延期ID为81的项目,查看延期后的日期 >> 2023-07-01
延期ID为0的项目,返回空 >> 0
*/
global $tester;
$tester->loadModel('project');
$_POST['end'] = '2023-07-01';
$changes1 = $tester->project->putoff(81);
$changes2 = $tester->project->putoff(0);
r($changes1[0]) && p('new') && e('2023-07-01'); // 延期ID为81的项目,查看延期后的日期
r($changes2) && p() && e('0'); // 延期ID为0的项目,返回空
+4 -4
View File
@@ -12,10 +12,10 @@ title=测试 projectModel->start();
timeout=0
cid=1
- 执行project模块的start方法,参数是11, $data- ,属性0 @status
@status
- 执行project模块的start方法,参数是12, $data- ,属性0 @doing
@doing
- 执行project模块的start方法,参数是11, $data,属性0 @status
- 执行project模块的start方法,参数是12, $data,属性0 @doing
- 执行project模块的start方法,参数是13, $data
- 属性name @0
- 属性status @0
+16 -6
View File
@@ -40,12 +40,9 @@ class Project
* @access public
* @return void
*/
public function create($params)
public function create($project, $postData)
{
$_POST = $params;
$projectID = $this->project->create();
unset($_POST);
$projectID = $this->project->create($project, $postData);
if(dao::isError()) return array('message' => dao::getError());
@@ -105,7 +102,7 @@ class Project
}
/**
* Test get all the projects under the program set to which an project belongs
* Activate a project.
*
* @param int $projectID
* @param object $project
@@ -116,4 +113,17 @@ class Project
{
return $this->project->activate($projectID, $project);
}
/**
* doActivate a project.
*
* @param int $projectID
* @param object $project
* @access public
* @return bool
*/
public function doActivate($projectID, $project)
{
return $this->project->doActivate($projectID, $project);
}
}
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
su('admin');
function initData()
{
$project = zdTable('project');
$project->id->range('2-5');
$project->project->range('2-5');
$project->name->prefix("项目")->range('2-5');
$project->code->prefix("project")->range('2-5');
$project->model->range("scrum");
$project->auth->range("[]");
$project->path->range("[]");
$project->type->range("project");
$project->grade->range("1");
$project->days->range("1");
$project->status->range("wait");
$project->desc->range("[]");
$project->budget->range("100000,200000");
$project->budgetUnit->range("CNY");
$project->percent->range("0-0");
$project->gen(4);
}
/**
title=测试 projectModel::fetchProjectInfo;
timeout=0
cid=1
*/
global $tester;
$tester->loadModel('project');
initData();
r($tester->project->fetchProjectInfo(2)) && p('code,type') && e('project2,project'); //获取ID等于11的项目
r($tester->project->fetchProjectInfo(1)) && p('code') && e('0'); //获取不存在的项目
+210 -21
View File
@@ -10,8 +10,148 @@ declare(strict_types=1);
*/
class projectZen extends project
{
/**
* Append extras data to post data.
*
* @param object $postData
* @access protected
* @return int|object
*/
protected function prepareCreateExtras(object $postData): object
{
$rawdata = $postData->rawdata;
$project = $postData->setDefault('status', 'wait')
->setIF($rawdata->delta == 999, 'end', LONG_TIME)
->setIF($rawdata->delta == 999, 'days', 0)
->setIF($rawdata->acl == 'open', 'whitelist', '')
->setIF(!isset($rawdata->whitelist), 'whitelist', '')
->setIF(!isset($rawdata->multiple), 'multiple', '1')
->setDefault('openedBy', $this->app->user->account)
->setDefault('openedDate', helper::now())
->setDefault('team', $rawdata->name)
->setDefault('lastEditedBy', $this->app->user->account)
->setDefault('lastEditedDate', helper::now())
->setDefault('days', '0')
->add('type', 'project')
->join('whitelist', ',')
->join('auth', ',')
->stripTags($this->config->project->editor->create['id'], $this->config->allowedTags)
->get();
if(!isset($this->config->setCode) or $this->config->setCode == 0) unset($project->code);
/* Lean mode relation defaultProgram. */
if($this->config->systemMode == 'light') $project->parent = $this->config->global->defaultProgram;
if(!$this->checkProductAndBranch($rawdata, $project)) return false;
if(!$this->checkDaysAndBudget($rawdata, $project)) return false;
if(!$this->checkProductNameUnqiue($rawdata, $project)) return false;
return $project;
}
private function checkProductAndBranch(object $rawdata, object $project): bool
{
$linkedProductsCount = 0;
if($project->hasProduct && isset($rawdata->products))
{
foreach($rawdata->products as $product)
{
if(!empty($product)) $linkedProductsCount++;
}
}
if($rawdata->products)
{
$topProgramID = $this->loadModel('program')->getTopByID($project->parent);
$multipleProducts = $this->loadModel('product')->getMultiBranchPairs($topProgramID);
foreach($rawdata->products as $index => $productID)
{
if(isset($multipleProducts[$productID]) and empty($rawdata->branch[$index]))
{
dao::$errors[] = $this->lang->project->error->emptyBranch;
return false;
}
}
}
$program = new stdClass();
if($project->parent)
{
$program = $this->dao->select('*')->from(TABLE_PROGRAM)->where('id')->eq($project->parent)->fetch();
/* Judge products not empty. */
if($project->hasProduct && empty($linkedProductsCount) and !isset($rawdata->newProduct))
{
dao::$errors['products0'] = $this->lang->project->error->productNotEmpty;
return false;
}
}
return true;
}
private function checkDaysAndBudget(object $rawdata, object $project): bool
{
/* 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;
}
if(!empty($project->budget))
{
if(!is_numeric($project->budget))
{
dao::$errors['budget'] = sprintf($this->lang->project->error->budgetNumber);
return false;
}
else if(is_numeric($project->budget) and ($project->budget < 0))
{
dao::$errors['budget'] = sprintf($this->lang->project->error->budgetGe0);
return false;
}
else
{
$project->budget = round((float)$rawdata->budget, 2);
}
}
return true;
}
private function checkProductNameUnqiue(object $rawdata, object $project): bool
{
/* When select create new product, product name cannot be empty and duplicate. */
if($project->hasProduct && isset($rawdata->newProduct))
{
if(empty($rawdata->productName))
{
$this->app->loadLang('product');
dao::$errors['productName'] = sprintf($this->lang->error->notempty, $this->lang->product->name);
return false;
}
else
{
$programID = isset($project->parent) ? $project->parent : 0;
$existProductName = $this->dao->select('name')->from(TABLE_PRODUCT)->where('name')->eq($rawdata->productName)->andWhere('program')->eq($programID)->fetch('name');
if(!empty($existProductName))
{
dao::$errors['productName'] = $this->lang->project->error->existProductName;
return false;
}
}
}
return true;
}
/**
* Send variables to create page.
*
* @param string $model
* @param int $programID
* @param int $copyProjectID
@@ -19,9 +159,10 @@ class projectZen extends project
* @access protected
* @return void
*/
protected function buildCreateForm(string $model, int $programID, int $copyProjectID, string $extra):void
protected function buildCreateForm(string $model, int $programID, int $copyProjectID, string $extra): void
{
$this->loadModel('product');
$this->loadModel('program');
$extra = str_replace(array(',', ' '), array('&', ''), $extra);
parse_str($extra, $output);
@@ -29,20 +170,9 @@ class projectZen extends project
if($this->app->tab == 'program' and $programID) $this->loadModel('program')->setMenu($programID);
if($this->app->tab == 'product' and !empty($output['productID'])) $this->loadModel('product')->setMenu($output['productID']);
if($this->app->tab == 'doc') unset($this->lang->doc->menu->project['subMenu']);
$this->session->set('projectModel', $model);
if($copyProjectID)
{
$copyProject = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($copyProjectID)->fetch();
$products = $this->product->getProducts($copyProjectID);
if(!$copyProject->hasProduct) $shadow = 1;
foreach($products as $product)
{
$branches = implode(',', $product->branches);
$copyProject->productPlans[$product->id] = $this->loadModel('productplan')->getPairs($product->id, $branches, 'noclosed', true);
}
}
if($copyProjectID) $copyProject = $this->copyProject((int)$copyProjectID);
$shadow = empty($copyProject->hasProduct) ? 1 : 0;
if($this->view->globalDisableProgram) $programID = $this->config->global->defaultProgram;
$topProgramID = $this->program->getTopByID($programID);
@@ -53,10 +183,6 @@ class projectZen extends project
$this->lang->project->subAclList = $this->lang->project->kanbanSubAclList;
}
$sprintConcept = empty($this->config->custom->sprintConcept) ?
$this->config->executionCommonList[$this->app->getClientLang()][0] :
$this->config->executionCommonList[$this->app->getClientLang()][1];
$withProgram = $this->config->systemMode == 'ALM' ? true : false;
$allProducts = array('0' => '') + $this->program->getProductPairs($programID, 'all', 'noclosed', '', $shadow, $withProgram);
$parentProgram = $this->loadModel('program')->getByID($programID);
@@ -66,13 +192,10 @@ class projectZen extends project
$this->view->model = $model;
$this->view->pmUsers = $this->loadModel('user')->getPairs('noclosed|nodeleted|pmfirst');
$this->view->users = $this->user->getPairs('noclosed|nodeleted');
$this->view->products = $products;
$this->view->programID = $programID;
$this->view->productID = isset($output['productID']) ? $output['productID'] : 0;
$this->view->branchID = isset($output['branchID']) ? $output['branchID'] : 0;
$this->view->allProducts = $allProducts;
$this->view->productPlans = array('0' => '') + $productPlans;
$this->view->branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products), 'noclosed');
$this->view->multiBranchProducts = $this->product->getMultiBranchPairs($topProgramID);
$this->view->copyProjects = $this->project->getPairsByModel($model);
$this->view->copyProjectID = $copyProjectID;
@@ -85,6 +208,72 @@ class projectZen extends project
$this->display();
}
/**
* Get copy project and send variables to create page.
*
* @param int $copyProjectID
* @access protected
* @return void
*/
private function getCopyProject(int $copyProjectID): object
{
$copyProject = $this->project->getByID($copyProjectID);
$products = $this->product->getProducts($copyProjectID);
foreach($products as $product)
{
$branches = implode(',', $product->branches);
$copyProject->productPlans[$product->id] = $this->loadModel('productplan')->getPairs($product->id, $branches, 'noclosed', true);
}
$this->view->branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products), 'noclosed');
$this->view->products = $products;
$this->view->copyProject = $copyProject;
return $copyProject;
}
/**
* Link plan's stories after create a project.
*
* @param object $postData
* @access protected
* @return void
*/
protected function linkPlanStories(object $postData)
{
$planIdList = array();
foreach($postData->rawdata->plans as $plans)
{
foreach($plans as $planID)
{
$planIdList[$planID] = $planID;
}
}
$planStoryGroup = $this->loadModel('story')->getStoriesByPlanIdList($planIdList);
foreach($planIdList as $planID)
{
$planStories = $planProducts = array();
$planStory = isset($planStoryGroup[$planID]) ? $planStoryGroup[$planID] : array();
if(!empty($planStory))
{
foreach($planStory as $id => $story)
{
if($story->status == 'draft' or $story->status == 'reviewing')
{
unset($planStory[$id]);
continue;
}
$planProducts[$story->id] = $story->product;
}
$planStories = array_keys($planStory);
$this->loadModel('execution')->linkStory($projectID, $planStories, $planProducts);
}
}
}
/**
* Append extras data to post data.
*
+11
View File
@@ -0,0 +1,11 @@
<?php
$config->task->form = new stdclass();
global $app;
$config->task->form->assign = array();
$config->task->form->assign['assignedTo'] = array('type' => 'string', 'required' => false, 'default' => '');
$config->task->form->assign['left'] = array('type' => 'float', 'required' => true);
$config->task->form->assign['lastEditedBy'] = array('type' => 'string', 'required' => false, 'default' => $app->user->account);
$config->task->form->assign['lastEditedDate'] = array('type' => 'date', 'required' => false, 'default' => helper::now());
$config->task->form->assign['assignedDate'] = array('type' => 'date', 'required' => false, 'default' => helper::now());
$config->task->form->assign['comment'] = array('type' => 'text', 'required' => false, 'default' => '');
+8 -54
View File
@@ -808,68 +808,22 @@ class task extends control
if(!empty($_POST))
{
$this->loadModel('action');
$changes = $this->task->assign($taskID);
$data = form::data($this->config->task->form->assign)->get();
$postComment = $data->comment;
unset($data->comment);
if(dao::isError())
{
if($this->viewType == 'json' or (defined('RUN_MODE') && RUN_MODE == 'api')) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return print(js::error(dao::getError()));
}
$changes = $this->task->assign($data, $taskID);
if(dao::isError()) return $this->taskZen->errorAfterAssignTo();
$actionID = $this->action->create('task', $taskID, 'Assigned', $this->post->comment, $this->post->assignedTo);
$actionID = $this->loadModel('action')->create('task', $taskID, 'Assigned', $postComment, $this->post->assignedTo);
$this->action->logHistory($actionID, $changes);
$this->executeHooks($taskID);
if($this->viewType == 'json' or (defined('RUN_MODE') && RUN_MODE == 'api')) return $this->send(array('result' => 'success'));
if(isonlybody())
{
$task = $this->task->getById($taskID);
$execution = $this->execution->getByID($task->execution);
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
if(($this->app->tab == 'execution' or ($this->config->vision == 'lite' and $this->app->tab == 'project' and $this->session->kanbanview == 'kanban')) and $execution->type == 'kanban')
{
$rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : '';
$kanbanData = $this->loadModel('kanban')->getRDKanban($task->execution, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue);
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)"));
}
if($from == 'taskkanban')
{
$taskSearchValue = $this->session->taskSearchValue ? $this->session->taskSearchValue : '';
$kanbanData = $this->loadModel('kanban')->getExecutionKanban($task->execution, $execLaneType, $execGroupBy, $taskSearchValue);
$kanbanType = $execLaneType == 'all' ? 'task' : key($kanbanData);
$kanbanData = $kanbanData[$kanbanType];
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban(\"task\", $kanbanData)"));
}
return print(js::closeModal('parent.parent', 'this'));
}
return print(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent'));
return $this->taskZen->reponseAfterAssignTo($changes);
}
$members = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted');
/* Compute next assignedTo. */
if(!empty($task->team) and strpos('done,cencel,closed', $task->status) === false)
{
$task->nextUser = $this->task->getAssignedTo4Multi($task->team, $task, 'next');
$members = $this->task->getMemberPairs($task);
}
if(!isset($members[$task->assignedTo])) $members[$task->assignedTo] = $task->assignedTo;
if(isset($members['closed']) or $task->status == 'closed') $members['closed'] = 'Closed';
$this->view->title = $this->view->execution->name . $this->lang->colon . $this->lang->task->assign;
$this->view->position[] = $this->lang->task->assign;
$this->view->task = $task;
$this->view->members = $members;
$this->view->users = $this->loadModel('user')->getPairs();
$this->display();
$this->taskZen->buildAssignToForm($executionID, $task);
}
/**
+10 -43
View File
@@ -1595,24 +1595,16 @@ class taskModel extends model
/**
* Assign a task to a user again.
*
* @param object $task
* @param int $taskID
* @access public
* @return void
* @return array
*/
public function assign($taskID)
public function assign($task, $taskID): array|false
{
$task->id = $taskID;
$oldTask = $this->getById($taskID);
$now = helper::now();
$task = fixer::input('post')
->add('id', $taskID)
->cleanFloat('left')
->setDefault('lastEditedBy', $this->app->user->account)
->setDefault('lastEditedDate', $now)
->setDefault('assignedDate', $now)
->stripTags($this->config->task->editor->assignto['id'], $this->config->allowedTags)
->remove('comment,showModule')
->get();
if($oldTask->status != 'done' and $oldTask->status != 'closed' and isset($task->left) and $task->left == 0)
{
dao::$errors[] = sprintf($this->lang->error->notempty, $this->lang->task->left);
@@ -2552,42 +2544,17 @@ class taskModel extends model
public function getUserTasks($account, $type = 'assignedTo', $limit = 0, $pager = null, $orderBy = "id_desc", $projectID = 0)
{
if(!$this->loadModel('common')->checkField(TABLE_TASK, $type)) return array();
$orderBy = str_replace('pri_', 'priOrder_', $orderBy);
$orderBy = str_replace('project_', 't1.project_', $orderBy);
$tasks = $this->dao->select("t1.*, t4.id as project, t2.id as executionID, t2.name as executionName, t4.name as projectName, t2.multiple as executionMultiple, t2.type as executionType, t3.id as storyID, t3.title as storyTitle, t3.status AS storyStatus, t3.version AS latestStoryVersion, IF(t1.`pri` = 0, {$this->config->maxPriValue}, t1.`pri`) as priOrder")
->from(TABLE_TASK)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on("t1.execution = t2.id")
->leftJoin(TABLE_STORY)->alias('t3')->on('t1.story = t3.id')
->leftJoin(TABLE_PROJECT)->alias('t4')->on("t2.project = t4.id")
->leftJoin(TABLE_TASKTEAM)->alias('t5')->on("t5.task = t1.id and t5.account = '{$account}'")
->where('t1.deleted')->eq(0)
->andWhere('t2.deleted')->eq(0)
->beginIF($this->config->vision)->andWhere('t1.vision')->eq($this->config->vision)->fi()
->beginIF($this->config->vision)->andWhere('t2.vision')->eq($this->config->vision)->fi()
->beginIF($type != 'closedBy' and $this->app->moduleName == 'block')->andWhere('t1.status')->ne('closed')->fi()
->beginIF($projectID)->andWhere('t1.project')->eq($projectID)->fi()
->beginIF(!$this->app->user->admin)->andWhere('t1.execution')->in($this->app->user->view->sprints)->fi()
->beginIF($type == 'finishedBy')
->andWhere('t1.finishedby', 1)->eq($account)
->orWhere('t5.status')->eq("done")
->markRight(1)
->fi()
->beginIF($type == 'assignedTo' and ($this->app->rawModule == 'my' or $this->app->rawModule == 'block'))->andWhere('t2.status', true)->ne('suspended')->orWhere('t4.status')->ne('suspended')->markRight(1)->fi()
->beginIF($type != 'all' and $type != 'finishedBy' and $type != 'assignedTo')->andWhere("t1.`$type`")->eq($account)->fi()
->beginIF($type == 'assignedTo')->andWhere("(t1.assignedTo = '{$account}' or (t1.mode = 'multi' and t5.`account` = '{$account}' and t1.status != 'closed' and t5.status != 'done') )")->fi()
->beginIF($type == 'assignedTo' and $this->app->rawModule == 'my' and $this->app->rawMethod == 'work')->andWhere('t1.status')->notin('closed,cancel')->fi()
->orderBy($orderBy)
->beginIF($limit > 0)->limit($limit)->fi()
->page($pager, 't1.id')
->fetchAll('id');
$tasks = $this->taskTao->fetchUserTasksByType($account, $type, $orderBy, $projectID, $limit, $pager);
if(!$tasks) return array();
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'task', false);
$taskTeam = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in(array_keys($tasks))->fetchGroup('task');
$taskTeam = $this->taskTao->getTeamMembersByIdList(array_keys($tasks));
foreach($taskTeam as $taskID => $team) $tasks[$taskID]->team = $team;
if($tasks) return $this->processTasks($tasks);
return array();
return $this->processTasks($tasks);
}
/**
+45 -2
View File
@@ -113,8 +113,51 @@ class taskTao extends taskModel
}
/**
* Get task team members by id list.
* 通过任务ID列表查询任务团队成员信息。
* Fetch user tasks by type.
*
* @param string $account
* @param string $type assignedTo|finishedBy|closedBy
* @param string $orderBy
* @param int $projectID
* @access protected
* @return object[]
*/
protected function fetchUserTasksByType(string $account, string $type, string $orderBy, int $projectID, int $limit, object|null $pager): array
{
$orderBy = str_replace('pri_', 'priOrder_', $orderBy);
$orderBy = str_replace('project_', 't1.project_', $orderBy);
return $this->dao->select("t1.*, t4.id as project, t2.id as executionID, t2.name as executionName, t4.name as projectName, t2.multiple as executionMultiple, t2.type as executionType, t3.id as storyID, t3.title as storyTitle, t3.status AS storyStatus, t3.version AS latestStoryVersion, IF(t1.`pri` = 0, {$this->config->maxPriValue}, t1.`pri`) as priOrder")
->from(TABLE_TASK)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on("t1.execution = t2.id")
->leftJoin(TABLE_STORY)->alias('t3')->on('t1.story = t3.id')
->leftJoin(TABLE_PROJECT)->alias('t4')->on("t2.project = t4.id")
->leftJoin(TABLE_TASKTEAM)->alias('t5')->on("t5.task = t1.id and t5.account = '{$account}'")
->where('t1.deleted')->eq(0)
->andWhere('t2.deleted')->eq(0)
->beginIF($this->config->vision)->andWhere('t1.vision')->eq($this->config->vision)->fi()
->beginIF($this->config->vision)->andWhere('t2.vision')->eq($this->config->vision)->fi()
->beginIF($type != 'closedBy' and $this->app->moduleName == 'block')->andWhere('t1.status')->ne('closed')->fi()
->beginIF($projectID)->andWhere('t1.project')->eq($projectID)->fi()
->beginIF(!$this->app->user->admin)->andWhere('t1.execution')->in($this->app->user->view->sprints)->fi()
->beginIF($type == 'finishedBy')
->andWhere('t1.finishedby', 1)->eq($account)
->orWhere('t5.status')->eq("done")
->markRight(1)
->fi()
->beginIF($type == 'assignedTo' and ($this->app->rawModule == 'my' or $this->app->rawModule == 'block'))->andWhere('t2.status', true)->ne('suspended')->orWhere('t4.status')->ne('suspended')->markRight(1)->fi()
->beginIF($type != 'all' and $type != 'finishedBy' and $type != 'assignedTo')->andWhere("t1.`$type`")->eq($account)->fi()
->beginIF($type == 'assignedTo')->andWhere("(t1.assignedTo = '{$account}' or (t1.mode = 'multi' and t5.`account` = '{$account}' and t1.status != 'closed' and t5.status != 'done') )")->fi()
->beginIF($type == 'assignedTo' and $this->app->rawModule == 'my' and $this->app->rawMethod == 'work')->andWhere('t1.status')->notin('closed,cancel')->fi()
->orderBy($orderBy)
->beginIF($limit > 0)->limit($limit)->fi()
->page($pager, 't1.id')
->fetchAll('id');
}
/**
* Get task team by id list.
* 通过任务ID列表查询任务团队信息。
*
* @param array $taskIdList
* @access protected
+72 -19
View File
@@ -7,31 +7,84 @@ su('admin');
/**
title=taskModel->assign();
timeout=0
cid=1
pid=1
wait状态任务指派 >> assignedTo,po82,user92
doing状态任务指派 >> assignedTo,,user93
done状态任务指派 >> assignedTo,,user94
pause状态任务指派 >> assignedTo,,user95
cancel状态任务指派 >> assignedTo,,user96
closed状态任务指派 >> assignedTo,,user97
- 执行task模块的assign方法,参数是$taskIDlist[0],$waitTask
- 第0条的field属性 @assignedTo
- 第0条的old属性 @old1
- 第0条的new属性 @user92
- 执行task模块的assign方法,参数是$taskIDlist[0],$waitTaskLeft
- 第1条的field属性 @left
- 第1条的old属性 @0
- 第1条的new属性 @1
- 执行task模块的assign方法,参数是$taskIDlist[1],$doingTask
- 第0条的field属性 @assignedTo
- 第0条的old属性 @old2
- 第0条的new属性 @user93
- 执行task模块的assign方法,参数是$taskIDlist[2],$doneTask
- 第0条的field属性 @assignedTo
- 第0条的old属性 @old3
- 第0条的new属性 @user94
- 执行task模块的assign方法,参数是$taskIDlist[3],$pauseTask
- 第0条的field属性 @assignedTo
- 第0条的old属性 @old4
- 第0条的new属性 @user95
- 执行task模块的assign方法,参数是$taskIDlist[4],$cancelTask
- 第0条的field属性 @assignedTo
- 第0条的old属性 @old5
- 第0条的new属性 @user96
- 执行task模块的assign方法,参数是$taskIDlist[5],$closedTask
- 第0条的field属性 @assignedTo
- 第0条的old属性 @old6
- 第0条的new属性 @user97
*/
function initData()
{
$task = zdTable('task');
$task->id->range('1-6');
$task->execution->range('2,3,3,4');
$task->name->prefix("任务")->range('1-6');
$task->left->range('0');
$task->assignedTo->prefix("old")->range('1-6');
$task->status->range("wait,doing,done,pause,cancel,closed");
$task->gen(6);
$user = zdTable('user');
$user->id->range('1-100');
$user->account->range('1-100')->prefix('user');
$user->password->range('f8e41d6c31824c01e5d67c61a8ae49e9,e10adc3949ba59abbe56e057f20f883e');
$user->realname->range('1-100')->prefix("开发");
$user->gen(50);
}
initData();
$taskIDlist = array('1','2','3','4','5','6');
$waitTask = array('assignedTo' => 'user92','status' => 'wait', 'left' => '1');
$doingTask = array('assignedTo' => 'user93','status' => 'doing');
$doneTask = array('assignedTo' => 'user94','status' => 'done');
$pauseTask = array('assignedTo' => 'user95','status' => 'pause');
$cancelTask = array('assignedTo' => 'user96','status' => 'cancel');
$closedTask = array('assignedTo' => 'user97','status' => 'closed');
$waitTask = array('assignedTo' => 'user92','status' => 'wait');
$waitTaskLeft = array('assignedTo' => 'user91','status' => 'wait', 'left' => '1');
$doingTask = array('assignedTo' => 'user93','status' => 'doing');
$doneTask = array('assignedTo' => 'user94','status' => 'done');
$pauseTask = array('assignedTo' => 'user95','status' => 'pause');
$cancelTask = array('assignedTo' => 'user96','status' => 'cancel');
$closedTask = array('assignedTo' => 'user97','status' => 'closed');
$task = new taskTest();
r($task->assignTest($taskIDlist[0],$waitTask)) && p('0:field,old,new') && e('assignedTo,po82,user92'); // wait状态任务指派
r($task->assignTest($taskIDlist[1],$doingTask)) && p('0:field,old,new') && e('assignedTo,,user93'); // doing状态任务指派
r($task->assignTest($taskIDlist[2],$doneTask)) && p('0:field,old,new') && e('assignedTo,,user94'); // done状态任务指派
r($task->assignTest($taskIDlist[3],$pauseTask)) && p('0:field,old,new') && e('assignedTo,,user95'); // pause状态任务指派
r($task->assignTest($taskIDlist[4],$cancelTask)) && p('0:field,old,new') && e('assignedTo,,user96'); // cancel状态任务指派
r($task->assignTest($taskIDlist[5],$closedTask)) && p('0:field,old,new') && e('assignedTo,,user97'); // closed状态任务指派
r($task->assignTest($taskIDlist[0],$waitTask)) && p('0:field,old,new') && e('assignedTo,old1,user92'); // wait状态任务指派
r($task->assignTest($taskIDlist[0],$waitTaskLeft)) && p('1:field,old,new') && e('left,0,1'); // wait状态任务指派修改预计剩余
r($task->assignTest($taskIDlist[1],$doingTask)) && p('0:field,old,new') && e('assignedTo,old2,user93'); // doing状态任务指派
r($task->assignTest($taskIDlist[2],$doneTask)) && p('0:field,old,new') && e('assignedTo,old3,user94'); // done状态任务指派
r($task->assignTest($taskIDlist[3],$pauseTask)) && p('0:field,old,new') && e('assignedTo,old4,user95'); // pause状态任务指派
r($task->assignTest($taskIDlist[4],$cancelTask)) && p('0:field,old,new') && e('assignedTo,old5,user96'); // cancel状态任务指派
r($task->assignTest($taskIDlist[5],$closedTask)) && p('0:field,old,new') && e('assignedTo,old6,user97'); // closed状态任务指派
@@ -61,8 +61,98 @@ $members = array(array($members1, $members2), array($members3));
/**
title=taskModel->computeHours4Multiple();
timeout=0
cid=1
pid=1
- 执行task模块的computeHours4Multiple方法,参数是$oldTasks[0]
- 属性id @1
- 属性assignedTo @admin
- 属性status @doing
- 属性estimate @5
- 属性consumed @0
- 属性left @4
- 执行task模块的computeHours4Multiple方法,参数是$oldTasks[1]
- 属性id @2
- 属性assignedTo @user1
- 属性status @done
- 属性estimate @13
- 属性consumed @0
- 属性left @0
- 执行task模块的computeHours4Multiple方法,参数是$oldTasks[2]
- 属性id @3
- 属性assignedTo @admin
- 属性status @done
- 属性estimate @15
- 属性consumed @0
- 属性left @4
- 执行task模块的computeHours4Multiple方法,参数是$oldTasks[3]
- 属性id @4
- 属性assignedTo @user1
- 属性status @pause
- 属性estimate @17
- 属性consumed @0
- 属性left @2
- 执行task模块的computeHours4Multiple方法,参数是$oldTasks[4]
- 属性id @5
- 属性assignedTo @admin
- 属性status @cancel
- 属性estimate @0
- 属性consumed @0
- 属性left @0
- 执行task模块的computeHours4Multiple方法,参数是$oldTasks[0], $tasks[0]
- 属性id @1
- 属性assignedTo @admin
- 属性status @doing
- 属性estimate @5
- 属性consumed @0
- 属性left @4
- 执行task模块的computeHours4Multiple方法,参数是$oldTasks[1], $tasks[1]
- 属性id @2
- 属性assignedTo @user1
- 属性status @done
- 属性estimate @13
- 属性consumed @0
- 属性left @0
- 执行task模块的computehours4multiple方法,参数是$oldTasks[0], $tasks[0], $members[0]
- 属性id @1
- 属性assignedTo @admin
- 属性status @doing
- 属性estimate @3
- 属性consumed @0
- 属性left @3
- 执行task模块的computehours4multiple方法,参数是$oldTasks[1], $tasks[1], $members[1]
- 属性id @2
- 属性assignedTo @user1
- 属性status @done
- 属性estimate @3
- 属性consumed @0
- 属性left @3
- 执行task模块的computehours4multiple方法,参数是$oldTasks[0], $tasks[0], $members[0], false
- 属性id @1
- 属性assignedTo @admin
- 属性status @doing
- 属性estimate @3
- 属性consumed @0
- 属性left @3
- 执行task模块的computehours4multiple方法,参数是$oldTasks[1], $tasks[1], $members[1], false
- 属性id @2
- 属性assignedTo @user1
- 属性status @done
- 属性estimate @3
- 属性consumed @0
- 属性left @3
*/
@@ -77,4 +167,4 @@ r($task->computeHours4MultipleTest($oldTasks[1], $tasks[1]))
r($task->computehours4multipletest($oldTasks[0], $tasks[0], $members[0])) && p('id,assignedTo,status,estimate,consumed,left') && e('1,admin,doing,3,0,3'); // taskID 1 有传入task 传入members计算多人工时
r($task->computehours4multipletest($oldTasks[1], $tasks[1], $members[1])) && p('id,assignedTo,status,estimate,consumed,left') && e('2,user1,done,3,0,3'); // taskID 2 有传入task 传入members计算多人工时
r($task->computehours4multipletest($oldTasks[0], $tasks[0], $members[0], false)) && p('id,assignedTo,status,estimate,consumed,left') && e('1,admin,doing,3,0,3'); // taskID 1 有传入task 传入members 不自动更新状态计算多人工时
r($task->computehours4multipletest($oldTasks[1], $tasks[1], $members[1], false)) && p('id,assignedTo,status,estimate,consumed,left') && e('2,user1,done,3,0,3'); // taskID 2 有传入task 传入members 不自动更新状态计算多人工时
r($task->computehours4multipletest($oldTasks[1], $tasks[1], $members[1], false)) && p('id,assignedTo,status,estimate,consumed,left') && e('2,user1,done,3,0,3'); // taskID 2 有传入task 传入members 不自动更新状态计算多人工时
+73 -8
View File
@@ -2,20 +2,85 @@
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/task.class.php';
su('admin');
function initData()
{
$task = zdTable('task');
$task->id->range('1-20');
$task->name->range('1-20')->prefix('任务');
$task->module->range('1-5');
$task->parent->range('0{15},1{5}');
$task->execution->range('3-5');
$task->project->range('1');
$task->story->range('1-10');
$task->mode->range('[]{15},multi{3},linear{2}');
$task->storyVersion->range('1');
$task->deadline->range('20230212 000000:0')->type('timestamp')->format('YY/MM/DD');
$task->status->range('wait,doing{2},done{2},pause,cancel,closed');
$task->assignedTo->range('admin,user1');
$task->finishedBy->range('[]{3},user1{5}');
$task->closedBy->range('[]{7},user1{1}');
$task->pri->range('1-4');
$task->gen(20);
$execution = zdTable('project');
$execution->id->range('1-5');
$execution->name->range('项目1,项目2,迭代1,迭代2,迭代3');
$execution->type->range('project{2},sprint,stage,kanban');
$execution->status->range('doing{3},closed,doing');
$execution->parent->range('0,0,1,1,2');
$execution->project->range('0,0,1,1,2');
$execution->grade->range('1');
$execution->path->range('1,2,`1,3`,`1,4`,`2,5`')->prefix(',')->postfix(',');
$execution->begin->range('20230102 000000:0')->type('timestamp')->format('YY/MM/DD');
$execution->end->range('20230212 000000:0')->type('timestamp')->format('YY/MM/DD');
$execution->gen(5);
$story = zdTable('story');
$story->id->range('1-20');
$story->title->range('1-20')->prefix('需求');
$story->product->range('1-20');
$story->branch->range('0');
$story->version->range('1-2');
$story->status->range('active{10},draft{5},reviewing{2},closed{2},changing');
$story->gen(20);
zdTable('user')->gen(30);
$taskTeam = zdTable('taskteam');
$taskTeam->id->range('1-5');
$taskTeam->task->range('16{2},19{3}');
$taskTeam->account->range('admin,user1,admin,user1,user2');
$taskTeam->estimate->range('1{2},2{3}');
$taskTeam->left->range('1{2},1{3}');
$taskTeam->status->range('wait{2},doing{3}');
$taskTeam->gen(5);
$module = zdTable('module');
$module->root->range('1-5');
$module->type->range('story');
$module->gen(5);
}
/**
title=taskModel->getUserTasks();
timeout(0);
cid=1
pid=1
根据指派人员查看任务 >> 开发任务12
*/
$taskID = '2';
$assignedTo = 'user92';
*/
su('admin');
initData();
$task = new taskTest();
r($task->getUserTasksTest($taskID,$assignedTo)) && p('2:name') && e('开发任务12'); // 根据指派人员查看任务
r($task->getUserTasksTest('user1', 'assignedTo')) && p('20:name') && e('任务20'); // 查看指派给用户1的任务
r(count($task->getUserTasksTest('user1', 'assignedTo'))) && p() && e('10'); // 检查指派给用户1的任务数量
r($task->getUserTasksTest('user1', 'closedBy')) && p('16:name') && e('任务16'); // 查看由用户1关闭的任务
r(count($task->getUserTasksTest('user1', 'closedBy'))) && p() && e('2'); // 检查由用户1关闭的任务数量
r($task->getUserTasksTest('user1', 'finishedBy')) && p('20:name') && e('任务20'); // 查看由用户1完成的任务
r(count($task->getUserTasksTest('user1', 'finishedBy'))) && p() && e('11'); // 检查由用户1完成的任务数量
r(count($task->getUserTasksTest('user1', 'assignedTo', 8))) && p() && e('8'); // 查找8条指派给用户1的任务
r($task->getUserTasksTest('user1', 'finishedBy', 0, null, 'id_desc', 1)) && p('20:name') && e('任务20'); // 查看项目1下由用户1完成的任务
r(count($task->getUserTasksTest('user1', 'finishedBy', 0, null, 'id_desc', 1))) && p() && e('11'); // 查看项目1下由用户1完成的任务
+87 -1
View File
@@ -65,6 +65,92 @@ title=taskModel->computeCurrentTaskStatus();
timeout=0
cid=1
- 执行$task1
- 属性status @doing
- 属性assignedTo @user1
- 属性estimate @9
- 属性left @0
- 属性consumed @10
- 执行$task2
- 属性status @doing
- 属性assignedTo @user1
- 属性estimate @9
- 属性left @0
- 属性consumed @10
- 执行$task3
- 属性status @doing
- 属性assignedTo @user1
- 属性estimate @9
- 属性left @0
- 属性consumed @10
- 执行$task4
- 属性status @doing
- 属性assignedTo @user1
- 属性estimate @9
- 属性left @0
- 属性consumed @10
- 执行$task5
- 属性status @doing
- 属性assignedTo @user1
- 属性estimate @9
- 属性left @0
- 属性consumed @10
- 执行$task6
- 属性status @doing
- 属性assignedTo @user1
- 属性estimate @9
- 属性left @0
- 属性consumed @10
- 执行$task7
- 属性status @done
- 属性assignedTo @admin
- 属性estimate @8
- 属性left @8
- 属性consumed @0
- 执行$task8
- 属性status @done
- 属性assignedTo @admin
- 属性estimate @8
- 属性left @8
- 属性consumed @0
- 执行$task9
- 属性status @done
- 属性assignedTo @admin
- 属性estimate @8
- 属性left @8
- 属性consumed @0
- 执行$task10
- 属性status @done
- 属性assignedTo @admin
- 属性estimate @8
- 属性left @8
- 属性consumed @0
- 执行$task11
- 属性status @done
- 属性assignedTo @admin
- 属性estimate @8
- 属性left @8
- 属性consumed @0
- 执行$task12
- 属性status @done
- 属性assignedTo @admin
- 属性estimate @8
- 属性left @8
- 属性consumed @0
*/
$task = new taskTest();
@@ -92,4 +178,4 @@ r($task8) && p('status,assignedTo,estimate,left,consumed') && e('done,admin,8,8
r($task9) && p('status,assignedTo,estimate,left,consumed') && e('done,admin,8,8,0'); // 查询 task9 情况的task信息 currentTask[1] taskID 2 currentTasksestimate 状态自动变更 有工时消耗 团队成员members[0]
r($task10) && p('status,assignedTo,estimate,left,consumed') && e('done,admin,8,8,0'); // 查询 task10 情况的task信息 currentTask[1] taskID 2 currentTasksestimate 状态非自动变更 有工时消耗 团队成员members[0]
r($task11) && p('status,assignedTo,estimate,left,consumed') && e('done,admin,8,8,0'); // 查询 task11 情况的task信息 currentTask[1] taskID 2 currentTasksestimate 状态非自动变更 没有工时消耗 团队成员members[0]
r($task12) && p('status,assignedTo,estimate,left,consumed') && e('done,admin,8,8,0'); // 查询 task12 情况的task信息 currentTask[1] taskID 2 currentTasksestimate 状态非自动变更 没有工时消耗 团队成员members[1]
r($task12) && p('status,assignedTo,estimate,left,consumed') && e('done,admin,8,8,0'); // 查询 task12 情况的task信息 currentTask[1] taskID 2 currentTasksestimate 状态非自动变更 没有工时消耗 团队成员members[1]
+173 -112
View File
@@ -64,101 +64,162 @@ title=taskModel->fetchExecutionTasks();
timeout=0
cid=1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[0], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[0], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[0]- ,属性1 @任务1
@任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[1] @10
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[0], $modules[0], $orderBy[0], $count[0]- ,属性1 @任务1
@任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[0], $modules[0], $orderBy[0], $count[1] @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[1], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[1], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[2], $modules[0], $orderBy[0], $count[0]- ,属性1 @任务1
@任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[2], $modules[0], $orderBy[0], $count[1] @1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[3], $modules[0], $orderBy[0], $count[0]- ,属性1 @任务1
@任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[3], $modules[0], $orderBy[0], $count[1] @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[4], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[4], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[5], $modules[0], $orderBy[0], $count[0]- ,属性1 @任务1
@任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[5], $modules[0], $orderBy[0], $count[1] @1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[6], $modules[0], $orderBy[0], $count[0]- ,属性1 @任务1
@任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[6], $modules[0], $orderBy[0], $count[1] @1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[7], $modules[0], $orderBy[0], $count[0]- ,属性1 @任务1
@任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[7], $modules[0], $orderBy[0], $count[1] @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[8], $modules[0], $orderBy[0], $count[0]- ,属性1 @任务1
@任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[8], $modules[0], $orderBy[0], $count[1] @1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[9], $modules[0], $orderBy[0], $count[0]- ,属性11 @任务11
@任务11
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[9], $modules[0], $orderBy[0], $count[1] @1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[10], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[10], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[11], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[11], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[12], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[12], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[13], $modules[0], $orderBy[0], $count[0]- ,属性1 @任务1
@任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[13], $modules[0], $orderBy[0], $count[1] @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[1], $orderBy[0], $count[0]- ,属性17 @任务17
@任务17
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[1], $orderBy[0], $count[1] @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[2], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[2], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[3], $orderBy[0], $count[0]- ,属性17 @任务17
@任务17
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[3], $orderBy[0], $count[1] @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[0], $orderBy[1], $count[0]- ,属性1 @任务1
@任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[0], $orderBy[1], $count[1] @10
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[0]- ,属性18 @任务18
@任务18
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[1] @10
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[0], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[0], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[1], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[1], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[2], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[2], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[3], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[3], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[4], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[4], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[5], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[5], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[6], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[6], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[7], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[7], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[8], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[8], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[9], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[9], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[10], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[10], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[11], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[11], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[12], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[12], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[13], $modules[0], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[13], $modules[0], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[1], $orderBy[0], $count[0]- ,属性2 @任务2
@任务2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[1], $orderBy[0], $count[1] @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[2], $orderBy[0], $count[0] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[2], $orderBy[0], $count[1] @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[3], $orderBy[0], $count[0]- ,属性2 @任务2
@任务2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[3], $orderBy[0], $count[1] @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[0], $orderBy[1], $count[0]- ,属性20 @任务20
@任务20
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[0], $orderBy[1], $count[1] @10
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[0], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[0], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[0]第1条的name属性 @任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[1]属性 @10
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[0], $modules[0], $orderBy[0], $count[0]第1条的name属性 @任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[0], $modules[0], $orderBy[0], $count[1]属性 @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[1], $modules[0], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[1], $modules[0], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[2], $modules[0], $orderBy[0], $count[0]第1条的name属性 @任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[2], $modules[0], $orderBy[0], $count[1]属性 @1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[3], $modules[0], $orderBy[0], $count[0]第1条的name属性 @任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[3], $modules[0], $orderBy[0], $count[1]属性 @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[4], $modules[0], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[4], $modules[0], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[5], $modules[0], $orderBy[0], $count[0]第1条的name属性 @任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[5], $modules[0], $orderBy[0], $count[1]属性 @1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[6], $modules[0], $orderBy[0], $count[0]第1条的name属性 @任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[6], $modules[0], $orderBy[0], $count[1]属性 @1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[7], $modules[0], $orderBy[0], $count[0]第1条的name属性 @任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[7], $modules[0], $orderBy[0], $count[1]属性 @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[8], $modules[0], $orderBy[0], $count[0]第1条的name属性 @任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[8], $modules[0], $orderBy[0], $count[1]属性 @1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[9], $modules[0], $orderBy[0], $count[0]第11条的name属性 @任务11
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[9], $modules[0], $orderBy[0], $count[1]属性 @1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[10], $modules[0], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[10], $modules[0], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[11], $modules[0], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[11], $modules[0], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[12], $modules[0], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[12], $modules[0], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[13], $modules[0], $orderBy[0], $count[0]第1条的name属性 @任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[1], $type[13], $modules[0], $orderBy[0], $count[1]属性 @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[1], $orderBy[0], $count[0]第17条的name属性 @任务17
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[1], $orderBy[0], $count[1]属性 @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[2], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[2], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[3], $orderBy[0], $count[0]第17条的name属性 @任务17
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[3], $orderBy[0], $count[1]属性 @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[0], $orderBy[1], $count[0]第1条的name属性 @任务1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[1], $productIdList[0], $type[0], $modules[0], $orderBy[1], $count[1]属性 @10
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[0]第18条的name属性 @任务18
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[1]属性 @10
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[0], $modules[0], $orderBy[0], $count[0]第6条的name属性 @任务6
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[0], $modules[0], $orderBy[0], $count[1]属性 @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[1], $modules[0], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[1], $modules[0], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[2], $modules[0], $orderBy[0], $count[0]第6条的name属性 @任务6
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[2], $modules[0], $orderBy[0], $count[1]属性 @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[3], $modules[0], $orderBy[0], $count[0]第6条的name属性 @任务6
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[3], $modules[0], $orderBy[0], $count[1]属性 @1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[4], $modules[0], $orderBy[0], $count[0]第6条的name属性 @任务6
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[4], $modules[0], $orderBy[0], $count[1]属性 @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[5], $modules[0], $orderBy[0], $count[0]第6条的name属性 @任务6
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[5], $modules[0], $orderBy[0], $count[1]属性 @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[6], $modules[0], $orderBy[0], $count[0]第6条的name属性 @任务6
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[6], $modules[0], $orderBy[0], $count[1]属性 @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[7], $modules[0], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[7], $modules[0], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[8], $modules[0], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[8], $modules[0], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[9], $modules[0], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[9], $modules[0], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[10], $modules[0], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[10], $modules[0], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[11], $modules[0], $orderBy[0], $count[0]第6条的name属性 @任务6
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[11], $modules[0], $orderBy[0], $count[1]属性 @1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[12], $modules[0], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[12], $modules[0], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[13], $modules[0], $orderBy[0], $count[0]第6条的name属性 @任务6
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[1], $type[13], $modules[0], $orderBy[0], $count[1]属性 @1
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[1], $orderBy[0], $count[0]第2条的name属性 @任务2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[1], $orderBy[0], $count[1]属性 @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[2], $orderBy[0], $count[0]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[2], $orderBy[0], $count[1]属性 @0
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[3], $orderBy[0], $count[0]第2条的name属性 @任务2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[3], $orderBy[0], $count[1]属性 @2
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[0], $orderBy[1], $count[0]第20条的name属性 @任务20
- 执行task模块的fetchExecutionTasks方法,参数是$executionIdList[2], $productIdList[0], $type[0], $modules[0], $orderBy[1], $count[1]属性 @10
*/
@@ -215,20 +276,20 @@ r($task->fetchExecutionTasksTest($executionIdList[1], $productIdList[0], $type[
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[0])) && p('18:name') && e('任务18'); // 测试获取执行ID 2 product 0 type all module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[0], $type[0], $modules[0], $orderBy[0], $count[1])) && p() && e('10'); // 测试获取执行ID 2 product 0 type all module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[0], $modules[0], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 1 type all module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[0], $modules[0], $orderBy[0], $count[1])) && p() && e('0'); // 测试获取执行ID 2 product 1 type all module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[0], $modules[0], $orderBy[0], $count[0])) && p('6:name') && e('任务6'); // 测试获取执行ID 2 product 1 type all module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[0], $modules[0], $orderBy[0], $count[1])) && p() && e('2'); // 测试获取执行ID 2 product 1 type all module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[1], $modules[0], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 1 type assignedbyme module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[1], $modules[0], $orderBy[0], $count[1])) && p() && e('0'); // 测试获取执行ID 2 product 1 type assignedbyme module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[2], $modules[0], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 1 type myinvolved module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[2], $modules[0], $orderBy[0], $count[1])) && p() && e('0'); // 测试获取执行ID 2 product 1 type myinvolved module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[3], $modules[0], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 1 type undone module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[3], $modules[0], $orderBy[0], $count[1])) && p() && e('0'); // 测试获取执行ID 2 product 1 type undone module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[4], $modules[0], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 1 type needconfirm module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[4], $modules[0], $orderBy[0], $count[1])) && p() && e('0'); // 测试获取执行ID 2 product 1 type needconfirm module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[5], $modules[0], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 1 type assignedtome module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[5], $modules[0], $orderBy[0], $count[1])) && p() && e('0'); // 测试获取执行ID 2 product 1 type assignedtome module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[6], $modules[0], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 1 type finishedbyme module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[6], $modules[0], $orderBy[0], $count[1])) && p() && e('0'); // 测试获取执行ID 2 product 1 type finishedbyme module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[2], $modules[0], $orderBy[0], $count[0])) && p('6:name') && e('任务6'); // 测试获取执行ID 2 product 1 type myinvolved module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[2], $modules[0], $orderBy[0], $count[1])) && p() && e('2'); // 测试获取执行ID 2 product 1 type myinvolved module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[3], $modules[0], $orderBy[0], $count[0])) && p('6:name') && e('任务6'); // 测试获取执行ID 2 product 1 type undone module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[3], $modules[0], $orderBy[0], $count[1])) && p() && e('1'); // 测试获取执行ID 2 product 1 type undone module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[4], $modules[0], $orderBy[0], $count[0])) && p('6:name') && e('任务6'); // 测试获取执行ID 2 product 1 type needconfirm module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[4], $modules[0], $orderBy[0], $count[1])) && p() && e('2'); // 测试获取执行ID 2 product 1 type needconfirm module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[5], $modules[0], $orderBy[0], $count[0])) && p('6:name') && e('任务6'); // 测试获取执行ID 2 product 1 type assignedtome module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[5], $modules[0], $orderBy[0], $count[1])) && p() && e('2'); // 测试获取执行ID 2 product 1 type assignedtome module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[6], $modules[0], $orderBy[0], $count[0])) && p('6:name') && e('任务6'); // 测试获取执行ID 2 product 1 type finishedbyme module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[6], $modules[0], $orderBy[0], $count[1])) && p() && e('2'); // 测试获取执行ID 2 product 1 type finishedbyme module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[7], $modules[0], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 1 type delayed module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[7], $modules[0], $orderBy[0], $count[1])) && p() && e('0'); // 测试获取执行ID 2 product 1 type delayed module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[8], $modules[0], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 1 type wait module 空 orederBy 'status_asc, id_desc' 的任务
@@ -237,12 +298,12 @@ r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[9], $modules[0], $orderBy[0], $count[1])) && p() && e('0'); // 测试获取执行ID 2 product 1 type doing module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[10], $modules[0], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 1 type done module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[10], $modules[0], $orderBy[0], $count[1])) && p() && e('0'); // 测试获取执行ID 2 product 1 type done module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[11], $modules[0], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 1 type pause module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[11], $modules[0], $orderBy[0], $count[1])) && p() && e('0'); // 测试获取执行ID 2 product 1 type pause module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[11], $modules[0], $orderBy[0], $count[0])) && p('6:name') && e('任务6'); // 测试获取执行ID 2 product 1 type pause module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[11], $modules[0], $orderBy[0], $count[1])) && p() && e('1'); // 测试获取执行ID 2 product 1 type pause module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[12], $modules[0], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 1 type cancel module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[12], $modules[0], $orderBy[0], $count[1])) && p() && e('0'); // 测试获取执行ID 2 product 1 type cancel module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[13], $modules[0], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 1 type array('wait', 'doing', 'done', 'pause', 'cancel') module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[13], $modules[0], $orderBy[0], $count[1])) && p() && e('0'); // 测试获取执行ID 2 product 1 type array('wait', 'doing', 'done', 'pause', 'cancel') module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[13], $modules[0], $orderBy[0], $count[0])) && p('6:name') && e('任务6'); // 测试获取执行ID 2 product 1 type array('wait', 'doing', 'done', 'pause', 'cancel') module 空 orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[1], $type[13], $modules[0], $orderBy[0], $count[1])) && p() && e('1'); // 测试获取执行ID 2 product 1 type array('wait', 'doing', 'done', 'pause', 'cancel') module 空 orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[0], $type[0], $modules[1], $orderBy[0], $count[0])) && p('2:name') && e('任务2'); // 测试获取执行ID 2 product 0 type all module array(2) orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[0], $type[0], $modules[1], $orderBy[0], $count[1])) && p() && e('2'); // 测试获取执行ID 2 product 0 type all module array(2) orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[0], $type[0], $modules[2], $orderBy[0], $count[0])) && p() && e('0'); // 测试获取执行ID 2 product 0 type all module array(8) orederBy 'status_asc, id_desc' 的任务
@@ -250,4 +311,4 @@ r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[0], $type[
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[0], $type[0], $modules[3], $orderBy[0], $count[0])) && p('2:name') && e('任务2'); // 测试获取执行ID 2 product 0 type all module array(2,8) orederBy 'status_asc, id_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[0], $type[0], $modules[3], $orderBy[0], $count[1])) && p() && e('2'); // 测试获取执行ID 2 product 0 type all module array(2,8) orederBy 'status_asc, id_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[0], $type[0], $modules[0], $orderBy[1], $count[0])) && p('20:name') && e('任务20'); // 测试获取执行ID 2 product 0 type all module 空 orederBy 'pri_desc' 的任务
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[0], $type[0], $modules[0], $orderBy[1], $count[1])) && p() && e('10'); // 测试获取执行ID 2 product 0 type all module 空 orederBy 'pri_desc' 的任务数量
r($task->fetchExecutionTasksTest($executionIdList[2], $productIdList[0], $type[0], $modules[0], $orderBy[1], $count[1])) && p() && e('10'); // 测试获取执行ID 2 product 0 type all module 空 orederBy 'pri_desc' 的任务数量
@@ -18,10 +18,9 @@ title=taskModel->getTeamMembersByIdList();
timeout=0
cid=1
- 执行$emptyData @0
- 执行count($taskTeamGroup) @2
- 执行$firstTaskTeam- ,属性0 @1
@admin
- 执行$emptyData属性 @0
- 执行count($taskTeamGroup)属性 @2
- 执行$firstTaskTeam第0条的account属性 @admin
*/
@@ -33,6 +32,6 @@ $emptyData = $tester->task->getTeamMembersByIdList(array());
$taskTeamGroup = $tester->task->getTeamMembersByIdList($taskIdList);
$firstTaskTeam = current($taskTeamGroup);
r($emptyData) && p() && e('0'); // 测试传入空的taskIdList
r(count($taskTeamGroup)) && p() && e('2'); // 测试查询给定taskIdList的任务数量
r($firstTaskTeam) && p('0:account') && e('admin'); // 测试查询任务id为1团队中第一个人的用户名
r($emptyData) && p() && e('0'); // 测试传入空的taskIdList
r(count($taskTeamGroup)) && p() && e('2'); // 测试查询给定taskIdList的任务数量
r($firstTaskTeam) && p('0:account') && e('admin'); // 测试查询任务id为1团队中第一个人的用户名
+7 -8
View File
@@ -290,7 +290,10 @@ class taskTest
$createFields = array('assignedTo' => '', 'status' => '', 'comment' => '');
foreach($createFields as $field => $defaultValue) $_POST[$field] = $defaultValue;
foreach($param as $key => $value) $_POST[$key] = $value;
$object = $this->objectModel->assign($taskID);
$task = $_POST;
unset($task['comment']);
$object = $this->objectModel->assign((object)$task, $taskID);
unset($_POST);
if(dao::isError())
{
@@ -511,13 +514,9 @@ class taskTest
* @access public
* @return array
*/
public function getUserTasksTest($taskID, $assignedTo)
public function getUserTasksTest($account, $type = 'assignedTo', $limit = 0, $pager = null, $orderBy = 'id_desc', $projectID = 0)
{
$createFields = array('assignedTo' => $assignedTo, 'status' => 'doing', 'comment' => '');
foreach($createFields as $field => $defaultValue) $_POST[$field] = $defaultValue;
$this->objectModel->assign($taskID);
$object = $this->objectModel->getUserTasks($assignedTo);
unset($_POST);
$object = $this->objectModel->getUserTasks($account, $type, $limit, $pager, $orderBy, $projectID);
if(dao::isError())
{
return dao::getError();
@@ -1524,7 +1523,7 @@ class taskTest
* @access public
* @return array
*/
public function fetchExecutionTasksTest(int $executionID, int $productID = 0, string $type = 'all', array $modules = array(), string $orderBy = 'status_asc, id_desc', string $count = '0'): array|int
public function fetchExecutionTasksTest(int $executionID, int $productID = 0, string|array $type = 'all', array $modules = array(), string $orderBy = 'status_asc, id_desc', string $count = '0'): array|int
{
$tasks = $this->objectModel->fetchExecutionTasks($executionID, $productID, $type, $modules, $orderBy);
if(dao::isError())
+53 -32
View File
@@ -1,52 +1,73 @@
<?php
/**
* The assignto view of task of ZenTaoPMS.
*/
/*
======= Attention ======
This file is generated by zin-tool, you should check the following to-do list.
+ Familiar with the use of these widgets in zin: .
+ Check the following variables which used in widgets: $assignedToOptions.
+ Check the origin code in module/task/view/assignto.html.php, and ensure that all features have been implemented.
+ Check the origin js code in module/task/js/common.js and module/task/js/assignto.js
+ Check the origin css code in module/task/css/common.css and module/task/css/assignto.css
+ Remove the comments which starts with "zin:"
+ Test according to the new design draft and the original implementation
*/
namespace zin;
global $lang;
/* ====== Preparing and processing page data ====== */
$items = [];
foreach($members as $key => $value)
{
$items[] = ['text' => $value, 'value' => $key];
}
/* zin: Set variables to define picker options for form */
$formTitle = $task->name;
$assignedToOptions = $members;
set::itemID($task->id);
set::title($task->name);
form
/* ====== Define the page structure with zin widgets ====== */
/* zin: Define the form in main content */
formPanel
(
set::title($formTitle), // The form title is diffrent from the page title,
formGroup
(
set::label($lang->assignedToAB),
set::name('assignedTo'),
set::control(['type' => 'select', 'items' => $items]),
set::width("1/3"),
set::name("assignedTo"),
set::label("指派"),
set::value((empty($task->team) or strpos('done,cencel,closed', $task->status) !== false) ? $task->assignedTo : $task->nextUser),
set::control("picker"),
set::items($assignedToOptions)
),
formGroup
(
set::label($lang->task->left),
div
set::width("1/3"),
set::label("预计剩余"),
inputGroup
(
setClass('input-control has-suffix'),
input
control(set(array
(
set::type('number'),
set::min(0),
set::name('left'),
set::id('left'),
),
h::label
(
setClass('input-control-suffix'),
$lang->workingHour
)
'name' => "left",
'id' => "left",
'value' => $task->left,
'disabled' => false,
'type' => "text"
))),
"小时"
)
),
formGroup
(
set::label($lang->comment),
set::name('comment'),
set::control(['type' => 'textarea']),
),
set::actions(['save'])
set::width("2/3"),
set::name("comment"),
set::label("备注"),
set::control("editor")
)
);
render('modalDialog');
/* ====== Render page ====== */
render();
+95
View File
@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
/**
* The zen file of task module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Shujie Tian <tianshujie@easysoft.ltd>
* @package task
* @link https://www.zentao.net
*/
class taskZen extends task
{
/**
* Reponse after assignto.
*
* @param int $taskID
* @access protected
* @return void
*/
protected function reponseAfterAssignTo(int $taskID): int
{
if($this->viewType == 'json' or (defined('RUN_MODE') && RUN_MODE == 'api')) return $this->send(array('result' => 'success'));
if(isonlybody())
{
$task = $this->task->getById($taskID);
$execution = $this->execution->getByID($task->execution);
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
if(($this->app->tab == 'execution' or ($this->config->vision == 'lite' and $this->app->tab == 'project' and $this->session->kanbanview == 'kanban')) and $execution->type == 'kanban')
{
$rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : '';
$kanbanData = $this->loadModel('kanban')->getRDKanban($task->execution, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue);
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)"));
}
if($from == 'taskkanban')
{
$taskSearchValue = $this->session->taskSearchValue ? $this->session->taskSearchValue : '';
$kanbanData = $this->loadModel('kanban')->getExecutionKanban($task->execution, $execLaneType, $execGroupBy, $taskSearchValue);
$kanbanType = $execLaneType == 'all' ? 'task' : key($kanbanData);
$kanbanData = $kanbanData[$kanbanType];
$kanbanData = json_encode($kanbanData);
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban(\"task\", $kanbanData)"));
}
return print(js::closeModal('parent.parent', 'this'));
}
return print(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent'));
}
/**
* Return the error after assignto.
*
* @access protected
* @return void
*/
protected function errorAfterAssignTo(): int
{
if($this->viewType == 'json' or (defined('RUN_MODE') && RUN_MODE == 'api')) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return print(js::error(dao::getError()));
}
/**
* Build AssignTo Form.
*
* @param int $executionID
* @param object $task
* @access protected
* @return void
*/
protected function buildAssignToForm(int $executionID, object $task): void
{
$this->loadModel('action');
$members = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted');
/* Compute next assignedTo. */
if(!empty($task->team) and strpos('done,cencel,closed', $task->status) === false)
{
$task->nextUser = $this->task->getAssignedTo4Multi($task->team, $task, 'next');
$members = $this->task->getMemberPairs($task);
}
if(!isset($members[$task->assignedTo])) $members[$task->assignedTo] = $task->assignedTo;
if(isset($members['closed']) or $task->status == 'closed') $members['closed'] = 'Closed';
$this->view->title = $this->view->execution->name . $this->lang->colon . $this->lang->task->assign;
$this->view->position[] = $this->lang->task->assign;
$this->view->task = $task;
$this->view->members = $members;
$this->view->users = $this->loadModel('user')->getPairs();
$this->display();
}
}
+20 -13
View File
@@ -1,13 +1,14 @@
<?php
declare(strict_types=1);
/**
* The control file of todo module of ZenTaoPMS.
* The control file of example 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 lanzongjun@easycorp.ltd;
* @author Lanzongjun <lanzongjun@easycorp.ltd>
* @package todo
* @link http://www.zentao.net
* @link https://www.zentao.net
*/
class todo extends control
{
@@ -20,6 +21,7 @@ class todo extends control
public function __construct()
{
parent::__construct();
$this->app->loadClass('date');
$this->loadModel('task');
$this->loadModel('bug');
@@ -33,7 +35,7 @@ class todo extends control
* @param string $date
* @param string $from todo|feedback|block
* @access public
* @return void
* @return int|void
*/
public function create(string $date = 'today', string $from = 'todo')
{
@@ -41,20 +43,21 @@ class todo extends control
if(!empty($_POST))
{
$formData = form::data($this->config->todo->create->form);
$todo = form::data($this->config->todo->create->form)
->remove(implode(',', $this->config->todo->moduleList) . ',uid')
->stripTags($this->config->todo->editor->create['id'], $this->config->allowedTags)
->get();
$todo = $this->todoZen->beforeCreate($formData);
$todoID = $this->todoZen->doCreate($todo);
$todoID = $this->todo->create($todo);
if($todoID === false) return print(js::error(dao::getError()));
$todo->id = $todoID;
$this->todoZen->afterCreate($todo);
if(!empty($_POST['idvalue'])) return $this->send(array('result' => 'success'));
if(!empty($_POST['objectID'])) return $this->send(array('result' => 'success'));
if($from == 'block')
{
// TODO
$todo = $this->todo->getById($todoID);
$todo->begin = date::formatTime($todo->begin);
return $this->send(array('result' => 'success', 'id' => $todoID, 'name' => $todo->name, 'pri' => $todo->pri, 'priName' => $this->lang->todo->priList[$todo->pri], 'time' => date(DT_DATE4, strtotime($todo->date)) . ' ' . $todo->begin));
@@ -292,7 +295,7 @@ class todo extends control
* @access public
* @return void
*/
public function start(string $todoID): string|int
public function start(string $todoID)
{
$todoID = (int)$todoID;
$todo = $this->todo->getById($todoID);
@@ -305,18 +308,22 @@ class todo extends control
}
/**
* 激活待办事项
* Activated todo.
*
* @param $todoID
* @param string $todoID
* @access public
* @return void
*/
public function activate($todoID)
public function activate(string $todoID)
{
$todo = $this->todo->getById($todoID);
$todoID = (int)$todoID;
$todo = $this->todo->getById($todoID);
if($todo->status == 'done' or $todo->status == 'closed') $this->todo->activate($todoID);
if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success'));
if(isonlybody()) return print(js::reload('parent.parent'));
echo js::reload('parent');
}
+33 -101
View File
@@ -1,13 +1,14 @@
<?php
declare(strict_types=1);
/**
* The model file of todo module of ZenTaoPMS.
* The model file of example module of ZenTaoPMS.
*
* @copyright Copyright 2009-2015 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Chunsheng Wang <chunsheng@cnezsoft.com>
* @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 Lanzongjun <lanzongjun@easycorp.ltd>
* @package todo
* @version $Id: model.php 5035 2013-07-06 05:21:58Z wyd621@gmail.com $
* @link http://www.zentao.net
* @link https://www.zentao.net
*/
class todoModel extends model
{
@@ -18,10 +19,13 @@ class todoModel extends model
* @param object $todo
* @return int|false
*/
public function create(object $todo): int|bool
public function create(object $todo): int|false
{
$todoID = $this->todoTao->insert($todo);
/* 处理 $todo 信息 */
$processedTodo = $this->todoTao->beforeCreate($todo);
if(!$processedTodo) return false;
$todoID = $this->todoTao->insert($processedTodo);
if(dao::isError()) return false;
return $todoID;
@@ -467,22 +471,23 @@ class todoModel extends model
}
/**
* CreateByCycle
* Create todo by cycle.
*
* @param int $todoList
* @param array $todoList
* @access public
* @return void
*/
public function createByCycle($todoList)
public function createByCycle(array $todoList): void
{
$this->loadModel('action');
$today = helper::today();
$now = helper::now();
$lastCycleList = $this->dao->select('*')->from(TABLE_TODO)->where('type')->eq('cycle')->andWhere('deleted')->eq('0')->andWhere('idvalue')->in(array_keys($todoList))->orderBy('date_asc')->fetchAll('idvalue');
$activedUsers = $this->dao->select('account')->from(TABLE_USER)->where('deleted')->eq(0)->fetchPairs('account', 'account');
$today = helper::today();
$now = helper::now();
$cycleList = $this->todoTao->getCycleList($todoList);
$validUsers = $this->dao->select('account')->from(TABLE_USER)->where('deleted')->eq(0)->fetchPairs('account', 'account');
foreach($todoList as $todoID => $todo)
{
if(!isset($activedUsers[$todo->account])) continue;
if(!isset($validUsers[$todo->account])) continue;
$todo->config = json_decode($todo->config);
$begin = $todo->config->begin;
@@ -491,19 +496,7 @@ class todoModel extends model
if(!empty($beforeDays) && $beforeDays > 0) $begin = date('Y-m-d', strtotime("$begin -{$beforeDays} days"));
if($today < $begin or (!empty($end) && $today > $end)) continue;
$newTodo = new stdclass();
$newTodo->account = $todo->account;
$newTodo->begin = $todo->begin;
$newTodo->end = $todo->end;
$newTodo->type = 'cycle';
$newTodo->idvalue = $todoID;
$newTodo->pri = $todo->pri;
$newTodo->name = $todo->name;
$newTodo->desc = $todo->desc;
$newTodo->status = 'wait';
$newTodo->private = isset($todo->private) ? $todo->private : '';
$newTodo->assignedTo = isset($todo->assignedTo) ? $todo->assignedTo : '';
$newTodo->assignedBy = isset($todo->assignedBy) ? $todo->assignedBy : '';
$newTodo = $this->todoTao->buildCycleTodo($todo);
if(isset($todo->assignedTo) and $todo->assignedTo) $newTodo->assignedDate = $now;
$start = strtotime($begin);
@@ -511,98 +504,37 @@ class todoModel extends model
foreach(range($start, $finish, 86400) as $today)
{
$today = date('Y-m-d', $today);
$lastCycle = zget($lastCycleList, $todoID, '');
$date = '';
$lastCycle = zget($cycleList, $todoID, '');
if($todo->config->type == 'day')
{
if(isset($todo->config->day))
{
$day = (int)$todo->config->day;
if($day <= 0) continue;
/* If no data, judge the interval from the begin time. */
if(empty($lastCycle))
{
$todayTime = new DateTime($today);
$beginTime = new DateTime($todo->config->begin);
$interval = $todayTime->diff($beginTime)->days;
if($interval != $day) continue;
$date = $today;
}
/* If have data, judge the interval from the last cycle time. */
if(!empty($lastCycle->date))
{
$todayTime = new DateTime($today);
$lastCycleTime = new DateTime($lastCycle->date);
$interval = $todayTime->diff($lastCycleTime)->days;
if($interval != $day) continue;
$date = date('Y-m-d', strtotime("{$lastCycle->date} +{$day} days"));
}
}
if(isset($todo->config->specifiedDate))
{
$date = $today;
$specifiedDate = $todo->config->specify->month + 1 . '-' . $todo->config->specify->day;
/* If not set cycle every year and have data, continue. */
if(!empty($lastCycle) and !isset($todo->config->cycleYear)) continue;
/* If set specified date, only judge month and day. */
if(date('m-d', strtotime($date)) != $specifiedDate) continue;
}
}
elseif($todo->config->type == 'week')
{
$week = date('w', strtotime($today));
if(strpos(",{$todo->config->week},", ",{$week},") !== false)
{
if(empty($lastCycle)) $date = $today;
if($lastCycle and $lastCycle->date < $today) $date = $today;
}
}
elseif($todo->config->type == 'month')
{
$day = date('j', strtotime($today));
if(strpos(",{$todo->config->month},", ",{$day},") !== false)
{
if(empty($lastCycle)) $date = $today;
if($lastCycle and $lastCycle->date < $today) $date = $today;
}
}
$date = $this->todoTao->getCycleTodoDate($todo, $lastCycle, $today);
if($date === false) continue;
if(!$date) continue;
if($date < $todo->config->begin) continue;
if($date < date('Y-m-d')) continue;
if($date > date('Y-m-d', $finish)) continue;
if($date < date('Y-m-d')) continue;
if($date > date('Y-m-d', $finish)) continue;
if(!empty($end) && $date > $end) continue;
if($lastCycle and ($date == $lastCycle->date)) continue;
$newTodo->date = $date;
$this->dao->insert(TABLE_TODO)->data($newTodo)->exec();
$this->todoTao->insert($newTodo);
$this->action->create('todo', $this->dao->lastInsertID(), 'opened', '', '', $newTodo->account);
$lastCycleList[$todoID] = $newTodo;
$cycleList[$todoID] = $newTodo;
}
}
}
/**
* 激活待办事项
* Activate todo.
*
* @param $todoID
*
* @param int $todoID
* @access public
* @return bool
*/
public function activate($todoID)
public function activate(int $todoID): bool
{
$this->dao->update(TABLE_TODO)->set('status')->eq('wait')->where('id')->eq((int)$todoID)->exec();
$this->dao->update(TABLE_TODO)->set('status')->eq('wait')->where('id')->eq($todoID)->exec();
$this->loadModel('action')->create('todo', $todoID, 'activated', '', 'wait');
return !dao::isError();
}
+238
View File
@@ -59,5 +59,243 @@ class todoTao extends todoModel
->where('id')->eq($todoID)
->exec();
return !dao::isError();
}
/*
* 处理要创建的todo的数据
* Processing todo data.
*
* @param object $todoData
* @return object|false
*/
protected function beforeCreate(object $todoData): object|false
{
$objectID = 0;
$hasObject = in_array($todoData->type, $this->config->todo->moduleList);
if($hasObject && $todoData->type) $objectID = $todoData->uid ? $todoData->type : $todoData->objectID;
$todoData->account = $this->app->user->account;
$todoData->assignedTo = zget($todoData, 'assignedTo', $this->app->user->account);
$todoData->assignedBy = zget($todoData, 'assignedBy', $this->app->user->account);
if($hasObject && $todoData->type) $todoData->objectID = $objectID;
if($todoData->status == 'done') $todoData->finishedBy = $this->app->user->account;
if($todoData->status == 'done') $todoData->finishedDate = helper::now();
if(!isset($todoData->pri) and in_array($todoData->type, $this->config->todo->moduleList) and !in_array($todoData->type, array('review', 'feedback')))
{
$todoData->pri = $this->dao->select('pri')->from($this->config->objectTables[$todoData->type])->where('id')->eq($todoData->objectID)->fetch('pri');
if($todoData->pri == 'high') $todoData->pri = 1;
if($todoData->pri == 'middle') $todoData->pri = 2;
if($todoData->pri == 'low') $todoData->pri = 3;
}
if($todoData->type != 'custom' and $todoData->objectID)
{
$type = $todoData->type;
$object = $this->loadModel($type)->getByID($todoData->{$type});
if(isset($object->name)) $todoData->name = $object->name;
if(isset($object->title)) $todoData->name = $object->title;
}
if($todoData->end < $todoData->begin)
{
dao::$errors[] = sprintf($this->lang->error->gt, $this->lang->todo->end, $this->lang->todo->begin);
return false;
}
if(!empty($todoData->cycle))
{
/* TODO confirmation. */
$todoData = $this->setCycle($todoData);
if(!$todoData) return false;
}
if(empty($todoData->cycle)) unset($todoData->config);
$todoData = $this->loadModel('file')->processImgURL($todoData, $this->config->todo->editor->create['id'], $this->post->uid);
return $todoData;
}
/**
* 获取周期待办列表
* Get cycle list.
* @param array $todoList
* @param string $orderBy
* @return array
*/
protected function getCycleList(array $todoList, string $orderBy = 'date_asc'): array
{
return $this->dao->select('*')
->from(TABLE_TODO)->where('type')->eq('cycle')
->andWhere('deleted')->eq('0')
->andWhere('objectID')->in(array_keys($todoList))
->orderBy($orderBy)
->fetchAll('objectID');
}
/**
* 通过待办构建周期待办数据
* Build cycle todo.
* @param object $todo
* @return stdclass
*/
protected function buildCycleTodo(object $todo): object
{
$newTodo = new stdclass();
$newTodo->account = $todo->account;
$newTodo->begin = $todo->begin;
$newTodo->end = $todo->end;
$newTodo->type = 'cycle';
$newTodo->objectID = $todo->id;
$newTodo->pri = $todo->pri;
$newTodo->name = $todo->name;
$newTodo->desc = $todo->desc;
$newTodo->status = 'wait';
$newTodo->private = $todo->private;
$newTodo->assignedTo = $todo->assignedTo;
$newTodo->assignedBy = $todo->assignedBy ;
return $newTodo;
}
/**
* 通过周期待办,获取要生成待办的日期
* Gets the date by the cycle todo.
* @param object $todo
* @param object $lastCycle
* @param string $today
* @return false|string
*/
protected function getCycleTodoDate(object $todo, object $lastCycle, string $today): false|string
{
$date = '';
if($todo->config->type == 'day')
{
return $this->getCycleDailyTodoDate($todo, $lastCycle, $today);
}
elseif($todo->config->type == 'week')
{
$week = date('w', strtotime($today));
if(strpos(",{$todo->config->week},", ",{$week},") !== false)
{
if(empty($lastCycle)) $date = $today;
if($lastCycle and $lastCycle->date < $today) $date = $today;
}
}
elseif($todo->config->type == 'month')
{
$day = date('j', strtotime($today));
if(strpos(",{$todo->config->month},", ",{$day},") !== false)
{
if(empty($lastCycle)) $date = $today;
if($lastCycle and $lastCycle->date < $today) $date = $today;
}
}
return $date;
}
/**
* 通过周期待办,获取要生成每日待办的日期
* Gets the daily todo date by the cycle todo.
* @param object $todo
* @param object $lastCycle
* @param string $today
* @return false|string
*/
private function getCycleDailyTodoDate(object $todo, object $lastCycle, string $today): false|string
{
$date = '';
if(isset($todo->config->day))
{
$day = (int)$todo->config->day;
if($day <= 0) return false;
/* If no data, judge the interval from the beginning time. */
if(empty($lastCycle))
{
$todayTime = new DateTime($today);
$beginTime = new DateTime($todo->config->begin);
$interval = $todayTime->diff($beginTime)->days;
if($interval != $day) return false;
$date = $today;
}
/* If data is available, determine the interval of time since the previous cycle. */
if(!empty($lastCycle->date))
{
$todayTime = new DateTime($today);
$lastCycleTime = new DateTime($lastCycle->date);
$interval = $todayTime->diff($lastCycleTime)->days;
if($interval != $day) return false;
$date = date('Y-m-d', strtotime("{$lastCycle->date} +{$day} days"));
}
}
if(isset($todo->config->specifiedDate))
{
$date = $today;
$specifiedDate = $todo->config->specify->month + 1 . '-' . $todo->config->specify->day;
/* If not set cycle every year and have data, continue. */
if(!empty($lastCycle) and !isset($todo->config->cycleYear)) return false;
/* If set specified date, only judge month and day. */
if(date('m-d', strtotime($date)) != $specifiedDate) return false;
}
return $date;
}
/**
* 设置周期待办数据
* Set cycle todo data.
*
* @param object $todoData
* @return false|object
*/
private function setCycle(object $todoData): false|object
{
$todoData->date = helper::today();
$todoData->config['begin'] = $todoData->date;
if($todoData->config['type'] == 'day')
{
unset($todoData->config['week'], $todoData->config['month']);
if(!$todoData->config['day'])
{
dao::$errors[] = sprintf($this->lang->error->notempty, $this->lang->todo->cycleDaysLabel);
return false;
}
if(!validater::checkInt($todoData->config['day']))
{
dao::$errors[] = sprintf($this->lang->error->int[0], $this->lang->todo->cycleDaysLabel);
return false;
}
}
if($todoData->config['type'] == 'week')
{
unset($todoData->config['day'], $todoData->config['month']);
$todoData->config['week'] = join(',', $todoData->config['week']);
}
if($todoData->config['type'] == 'month')
{
unset($todoData->config['day'], $todoData->config['week']);
$todoData->config['month'] = join(',', $todoData->config['month']);
}
if($todoData->config['beforeDays'] and !validater::checkInt($todoData->config['beforeDays']))
{
dao::$errors[] = sprintf($this->lang->error->int[0], $this->lang->todo->beforeDaysLabel);
return false;
}
$todoData->config['beforeDays'] = (int)$todoData->config['beforeDays'];
$todoData->config = json_encode($todoData->config);
$todoData->type = 'cycle';
return $todoData;
}
}
+36 -4
View File
@@ -4,15 +4,45 @@ include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/todo.class.php';
su('admin');
function initData()
{
$todo = zdTable('todo');
$todo->id->range('1-4');
$todo->account->prefix('admin')->range('1-4');
$todo->begin->range('1710');
$todo->end->range('1740');
$todo->feedback->range('0');
$todo->type->range('custom');
$todo->cycle->range('0');
$todo->idvalue->range('0');
$todo->pri->range("3");
$todo->name->prefix('测试待办')->range('1-4');
$todo->desc->range('描述');
$todo->status->range('wait');
$todo->private->range('0');
$todo->assignedTo->prefix('admin')->range('1-4');
$todo->assignedBy->prefix('admin')->range('1-4');
$todo->finishedBy->prefix('admin')->range('1-4');
$todo->closedBy->prefix('admin')->range('1-4');
$todo->deleted->range('0');
$todo->vision->range('1.0');
$todo->gen(4);
}
/**
title=测试 todoModel->activate();
timeout=0
cid=1
pid=1
激活一个状态为wait的todo >> wait
激活一个状态为doing的todo >> wait
激活一个状态为done的todo >> wait
- 执行todo模块的activate方法,参数是$todoIDList[0]属性status @wait
- 执行todo模块的activate方法,参数是$todoIDList[1]属性status @wait
- 执行todo模块的activate方法,参数是$todoIDList[2]属性status @wait
*/
@@ -20,6 +50,8 @@ $todoIDList = array('1', '2', '3');
$todo = new todoTest();
initData();
r($todo->activateTest($todoIDList[0])) && p('status') && e('wait'); // 激活一个状态为wait的todo
r($todo->activateTest($todoIDList[1])) && p('status') && e('wait'); // 激活一个状态为doing的todo
r($todo->activateTest($todoIDList[2])) && p('status') && e('wait'); // 激活一个状态为done的todo
+1 -56
View File
@@ -3,60 +3,6 @@ declare(strict_types=1);
class todoZen extends todo
{
/**
* 处理请求数据
* Processing request data.
*
* @param object $formData
* @return object|false
*/
protected function beforeCreate(object $formData): object|bool
{
$formData = $formData->remove(implode(',', $this->config->todo->moduleList) . ',uid')->stripTags($this->config->todo->editor->create['id'], $this->config->allowedTags)->get();
$idvalue = 0;
$hasObject = in_array($formData->type, $this->config->todo->moduleList);
if($hasObject && $formData->type) $idvalue = $formData->uid ? $formData->type : $formData->idvalue;
$formData->account = $this->app->user->account;
$formData->assignedTo = zget($formData, 'assignedTo', $this->app->user->account);
$formData->assignedBy = zget($formData, 'assignedBy', $this->app->user->account);
if($hasObject && $formData->type) $formData->idvalue = $idvalue;
if($formData->status == 'done') $formData->finishedBy = $this->app->user->account;
if($formData->status == 'done') $formData->finishedDate = helper::now();
if(!isset($formData->pri) and in_array($formData->type, $this->config->todo->moduleList) and $formData->type !== 'review' and $formData->type !== 'feedback')
{
// TODO
$formData->pri = $this->dao->select('pri')->from($this->config->objectTables[$formData->type])->where('id')->eq($formData->idvalue)->fetch('pri');
if($formData->pri == 'high') $formData->pri = 1;
if($formData->pri == 'middle') $formData->pri = 2;
if($formData->pri == 'low') $formData->pri = 3;
}
if($formData->type != 'custom' and $formData->idvalue)
{
$type = $formData->type;
$object = $this->loadModel($type)->getByID($formData->$type);
if(isset($object->name)) $formData->name = $object->name;
if(isset($object->title)) $formData->name = $object->title;
}
if($formData->end < $formData->begin)
{
dao::$errors[] = sprintf($this->lang->error->gt, $this->lang->todo->end, $this->lang->todo->begin);
return false;
}
if(!empty($formData->cycle)) $formData = $this->setCycle($formData);
else unset($formData->config);
$formData = $this->loadModel('file')->processImgURL($formData, $this->config->todo->editor->create['id'], $this->post->uid);
return $formData;
}
/**
* 创建待办
* Create a todo.
@@ -82,7 +28,6 @@ class todoZen extends todo
$this->loadModel('score')->create('todo', 'create', $todo->id);
// TODO
if(!empty($todo->cycle)) $this->todo->createByCycle(array($todo->id => $todo));
$this->loadModel('action')->create('todo', $todo->id, 'opened');
@@ -243,7 +188,7 @@ class todoZen extends todo
/**
* 输出确认弹框
* Output confirm alert.
*
*
* @param object $todo
* @access protected
* @return int
+1
View File
@@ -41,6 +41,7 @@ $config->installedVersion = $app->getInstalledVersion();
if($config->version != $config->installedVersion) die(header('location: upgrade.php'));
/* Run the app. */
$app->setStartTime($startTime);
$common = $app->loadCommon();
/* Check the request is getconfig or not. */
+1
View File
@@ -516,6 +516,7 @@
}).on('zui.locate', (e, data) =>
{
if(!data) return;
if(data === true) return loadCurrentPage();
if(typeof data === 'string') data = {url: data};
loadPage(data.url, data.selector);
});