From 68749bfa2a349e0ec63d18b6c573e60d56252e5a Mon Sep 17 00:00:00 2001 From: Wangyuting Date: Tue, 9 May 2023 15:50:20 +0800 Subject: [PATCH] * Adjust block test script. --- db/update20.0.sql | 1 + module/block/control.php | 2 +- module/block/lang/zh-cn.php | 23 +- module/block/model.php | 127 +++++++--- module/block/tao.php | 7 +- module/block/test/model/checkapi.php | 2 +- module/block/test/model/create.php | 2 +- module/block/test/model/deleteblock.php | 17 +- module/block/test/model/getadminblocklist.php | 42 ++++ .../block/test/model/getblockinitstatus.php | 8 +- module/block/test/model/getbyid.php | 8 +- .../block/test/model/getclosedblockpairs.php | 23 ++ .../test/model/getmaxorderbydashboard.php | 6 +- module/block/test/model/getmydashboard.php | 12 +- .../block/test/model/getspecifiedblockid.php | 6 +- module/block/test/model/initblock.php | 10 +- module/block/test/model/islongblock.php | 14 +- module/block/test/model/reset.php | 15 +- module/block/test/model/setorder.php | 6 +- module/block/test/model/update.php | 4 +- .../block/ui/productstatisticblock.html.php | 4 +- .../block/ui/projectstatisticblock.html.php | 4 +- module/block/ui/qastatisticblock.html.php | 4 +- module/block/zen.php | 19 +- module/programplan/model.php | 228 ++++++++++++++++-- .../test/model/yaml/getpairs/getpairs.yaml | 39 +++ .../yaml/getpairs/getprojectproduct.yaml | 13 + 27 files changed, 516 insertions(+), 130 deletions(-) mode change 100644 => 100755 module/block/test/model/deleteblock.php create mode 100644 module/block/test/model/getadminblocklist.php create mode 100755 module/block/test/model/getclosedblockpairs.php create mode 100644 module/programplan/test/model/yaml/getpairs/getpairs.yaml create mode 100644 module/programplan/test/model/yaml/getpairs/getprojectproduct.yaml diff --git a/db/update20.0.sql b/db/update20.0.sql index 334b6ef21a..50ed330a85 100644 --- a/db/update20.0.sql +++ b/db/update20.0.sql @@ -1,4 +1,5 @@ ALTER TABLE `zt_block` ADD `dashboard` varchar(20) NOT NULL DEFAULT '' AFTER `account`; +ALTER TABLE `zt_block` ADD `createdDate` date NULL AFTER `height`; ALTER TABLE `zt_block` CHANGE `block` `code` varchar(30) NOT NULL DEFAULT '' AFTER `module`; ALTER TABLE `zt_block` MODIFY `vision` varchar(10) NOT NULL DEFAULT 'rnd' AFTER `hidden`; diff --git a/module/block/control.php b/module/block/control.php index cc00bf40bc..80181ec33e 100755 --- a/module/block/control.php +++ b/module/block/control.php @@ -176,7 +176,7 @@ class block extends control /* 永久关闭区块。 */ $block = $this->block->getByID($blockID); - $this->block->deleteBlock($blockID); + $this->block->deleteBlock(0, $block->module, $block->code); /* 将关闭的区块保存到配置信息。 */ $closedBlock = isset($this->config->block->closed) ? $this->config->block->closed : ''; diff --git a/module/block/lang/zh-cn.php b/module/block/lang/zh-cn.php index 2a8b921da9..09c2f0bd7f 100644 --- a/module/block/lang/zh-cn.php +++ b/module/block/lang/zh-cn.php @@ -34,6 +34,7 @@ $lang->block->lblModule = '模块'; $lang->block->lblBlock = '区块'; $lang->block->lblNum = '条数'; $lang->block->lblHtml = 'HTML内容'; +$lang->block->html = 'HTML'; $lang->block->dynamic = '最新动态'; $lang->block->assignToMe = '待处理'; $lang->block->wait = '未开始'; @@ -421,12 +422,18 @@ $lang->block->availableBlocks['meeting'] = '会议'; $lang->block->availableBlocks['feedback'] = '反馈'; $lang->block->availableBlocks['ticket'] = '工单'; -$lang->block->moduleList['product'] = $lang->productCommon; -$lang->block->moduleList['project'] = $lang->projectCommon; -$lang->block->moduleList['execution'] = $lang->execution->common; -$lang->block->moduleList['qa'] = '测试'; -$lang->block->moduleList['todo'] = '待办'; -$lang->block->moduleList['doc'] = '文档'; +$lang->block->moduleList['product'] = $lang->productCommon; +$lang->block->moduleList['project'] = $lang->projectCommon; +$lang->block->moduleList['execution'] = $lang->execution->common; +$lang->block->moduleList['qa'] = '测试'; +$lang->block->moduleList['todo'] = '待办'; +$lang->block->moduleList['doc'] = '文档'; +$lang->block->moduleList['assigntome'] = $lang->block->assignToMe; +$lang->block->moduleList['dynamic'] = $lang->block->dynamic; +$lang->block->moduleList['guide'] = $lang->block->guide; +$lang->block->moduleList['welcome'] = $lang->block->welcome; +$lang->block->moduleList['html'] = $lang->block->html; +$lang->block->moduleList['contribute'] = $lang->block->contribute; $lang->block->modules['project'] = new stdclass(); $lang->block->modules['project']->availableBlocks['project'] = "{$lang->projectCommon}列表"; @@ -445,8 +452,8 @@ $lang->block->modules['waterfallProject'] = new stdclass(); $lang->block->modules['waterfallProject']->availableBlocks['waterfallgantt'] = "{$lang->projectCommon}计划"; $lang->block->modules['waterfallProject']->availableBlocks['projectdynamic'] = '最新动态'; -$lang->block->modules['agileplus']['index'] = $lang->block->modules['scrumProject']; -$lang->block->modules['waterfallplus']['index'] = $lang->block->modules['waterfallProject']; +$lang->block->modules['agileplus'] = $lang->block->modules['scrumProject']; +$lang->block->modules['waterfallplus'] = $lang->block->modules['waterfallProject']; $lang->block->modules['product'] = new stdclass(); $lang->block->modules['product']->availableBlocks['statistic'] = $lang->productCommon . '统计'; diff --git a/module/block/model.php b/module/block/model.php index a824516fb5..77b12629a3 100644 --- a/module/block/model.php +++ b/module/block/model.php @@ -13,8 +13,8 @@ declare(strict_types=1); class blockModel extends model { /** - * 检查然之请求时发起的hash是否匹配。 - * Check API for ranzhi. + * 检查ZDOO请求时发起的哈希值是否匹配。 + * Check if the hash value matches when requesting ZDOO. * * @param string $hash * @access public @@ -34,8 +34,8 @@ class blockModel extends model } /** - * 根据区块ID获取区块信息. - * Get a block by blockID. + * 根据区块ID获取区块信息。 + * Get a block by id. * * @param int $blockID * @access public @@ -54,13 +54,68 @@ class blockModel extends model } /** + * 获取被永久关闭的区块数据。 + * getClosedBlockPairs + * + * @param int $closedBlock + * @access public + * @return void + */ + public function getClosedBlockPairs($closedBlock) + { + $blockPairs = array(); + if(empty($closedBlock)) return $blockPairs; + + foreach(explode(',', $closedBlock) as $block) + { + $block = trim($block); + if(empty($block)) continue; + + list($moduleName, $blockKey) = explode('|', $block); + if($moduleName == $blockKey) + { + $blockPairs[$block] = $this->lang->block->moduleList[$moduleName]; + } + else + { + $blockName = $blockKey; + if(isset($this->lang->block->modules[$moduleName]->availableBlocks[$blockKey])) $blockName = $this->lang->block->modules[$moduleName]->availableBlocks[$blockKey]; + if(isset($this->lang->block->availableBlocks[$blockKey])) $blockName = $this->lang->block->availableBlocks[$blockKey]; + $blockPairs[$block] = "{$this->lang->block->moduleList[$moduleName]}|{$blockName}"; + } + } + + return $blockPairs; + } + + /** + * 获取后台信息区块参数信息。 + * get admin information block parameter information. + * + * @param string $startDate + * @access public + * @return array|false + */ + public function getAdminBlockList(string $startDate = ''): array|false + { + return $this->dao->select('code,params')->from(TABLE_BLOCK) + ->where('account')->eq('system') + ->andWhere('vision')->eq('rnd') + ->andWhere('dashboard')->eq('zentao') + ->andWhere('module')->eq('zentao') + ->andWhere('code')->in('plugin,patch,publicclass,news') + ->beginIF($startDate)->andWhere('createdDate')->ge($startDate)->fi() + ->fetchPairs(); + } + + /** + * 获取当前用户的区块列表。 * Get block list of current user. - * 获取区块列表. * * @param string $module * @param int $hidden * @access public - * @return int[]|false + * @return array|false */ public function getMyDashboard(string $dashboard, int $hidden = 0): array|false { @@ -68,12 +123,12 @@ class blockModel extends model } /** - * 获取隐藏的区块列表. - * Get hidden blocks + * 获取隐藏的区块列表。 + * Get hidden blocks. * * @param string $module * @access public - * @return int[]|false + * @return array|false */ public function getMyHiddenBlocks(string $dashboard): array|false { @@ -81,8 +136,8 @@ class blockModel extends model } /** - * 获取对应仪表盘下区块的最大排序号. - * Get max order number by block dashboard. + * 获取对应仪表盘下区块的最大排序号。 + * Get block max order number by dashboard. * * @param string $dashboard * @access public @@ -187,11 +242,11 @@ class blockModel extends model /** * 根据区块索引获取排序靠后的一个区块ID。 - * Get my block id by block code, - * - * @param string $dashboard - * @param string $module - * @param string $code + * Get the last block order on the block index, + * + * @param string $dashboard + * @param string $module + * @param string $code * @access public * @return int|false */ @@ -210,7 +265,7 @@ class blockModel extends model } /** - * 获取区块是否已经初始化的状态. + * 获取区块是否已经初始化的状态。 * get block is initiated or not. * * @param string $dashboard @@ -219,7 +274,7 @@ class blockModel extends model */ public function getBlockInitStatus(string $dashboard): bool { - $result = $this->dao->select('*')->from(TABLE_CONFIG) + $result = $this->dao->select('value')->from(TABLE_CONFIG) ->where('module')->eq($dashboard) ->andWhere('owner')->eq($this->app->user->account) ->andWhere('`section`')->eq('common') @@ -227,24 +282,24 @@ class blockModel extends model ->andWhere('vision')->eq($this->config->vision) ->fetch('value'); - return $result ? true : false; + return !empty($result); } /** * 检查此区块是否为长区块。 * Check whether long block. * - * @param object $block + * @param object $block * @access public * @return bool */ public function isLongBlock(object $block): bool { - return (!empty($block->grid) and $block->grid >= 6) ? true : false; + return (!empty($block->grid) and $block->grid >= 6); } /** - * 初始化用户某个仪表盘下的区块数据. + * 初始化用户某个仪表盘下的区块数据。 * Init block when account use first. * * @param string $dashboard @@ -286,7 +341,7 @@ class blockModel extends model * * @param object $formData * @access public - * @return void + * @return int */ public function create(object $formData): int|false { @@ -305,7 +360,7 @@ class blockModel extends model * * @param object $formData * @access public - * @return void + * @return int|false */ public function update(object $formData): int|false { @@ -326,7 +381,7 @@ class blockModel extends model * 对应仪表盘删除所有区块并更新为待初始化状态。 * Reset dashboard blocks. * - * @param string $dashboard + * @param string $dashboard * @access public * @return bool */ @@ -345,26 +400,33 @@ class blockModel extends model ->andWhere('`key`')->eq('blockInited') ->exec(); - return true; + return !dao::isError(); } /** - * 删除一个区块。 - * Delete a block. + * 根据ID删除一个区块或者根据代号删除多个区块。 + * Delete a block based on id or multiple blocks based on code. * * @param int $blockID * @access public * @return bool */ - public function deleteBlock(int $blockID): bool + public function deleteBlock(int $blockID = 0, string $module = '', string $code = ''): bool { $this->dao->delete()->from(TABLE_BLOCK) - ->where('id')->eq($blockID) + ->where('1=1') + ->beginIF($blockID) + ->andWhere('id')->eq($blockID) ->andWhere('account')->eq($this->app->user->account) ->andWhere('vision')->eq($this->config->vision) + ->fi() + ->beginIF($module and $code) + ->andWhere('module')->eq($module) + ->andWhere('code')->eq($code) + ->fi() ->exec(); - return true; + return !dao::isError(); } /** @@ -379,6 +441,7 @@ class blockModel extends model public function setOrder(int $blockID, int $order): bool { $this->dao->update(TABLE_BLOCK)->set('order')->eq($order)->where('id')->eq($blockID)->exec(); - return true; + + return !dao::isError(); } } diff --git a/module/block/tao.php b/module/block/tao.php index 1cbb176fd7..1b386c18ab 100644 --- a/module/block/tao.php +++ b/module/block/tao.php @@ -3,13 +3,13 @@ declare(strict_types=1); class blockTao extends blockModel { /** - * 获取当前用户的区块列表. + * 获取当前用户的区块列表。 * Get block list of current user. * * @param string $dashboard * @param int $hidden 0|1 * @access protected - * @return int[]|false + * @return array|false */ protected function fetchMyBlocks(string $dashboard, int $hidden = 0): array|false { @@ -23,6 +23,7 @@ class blockTao extends blockModel } /** + * 新增一个区块。 * Insert a block data. * * @param object $formData @@ -36,6 +37,6 @@ class blockTao extends blockModel ->batchCheck($this->config->block->create->requiredFields, 'notempty') ->exec(); - return true; + return !dao::isError(); } } diff --git a/module/block/test/model/checkapi.php b/module/block/test/model/checkapi.php index 3bc2f809af..b9b93889a6 100755 --- a/module/block/test/model/checkapi.php +++ b/module/block/test/model/checkapi.php @@ -1,6 +1,6 @@ #!/usr/bin/env php loadModel('block'); initData(); $blockTest = new blockTest(); -r($tester->block->getByID(2)) && p('id') && e("2"); // 测试ID为2的区块是否存在 -r($tester->block->deleteBlock(2)) && p('') && e('1'); // 测试ID为2的区块删除后的返回结果 -r($tester->block->getByID(2)) && p('id') && e("0"); // 测试ID为2的区块的是否存在 +r($tester->block->getByID(2)) && p('id') && e('2'); // 测试ID为2的区块是否存在 +r($tester->block->deleteBlock(2, '', '')) && p('') && e('1'); // 测试ID为2的区块删除后的返回结果 +r($tester->block->getByID(2)) && p('id') && e('0'); // 测试ID为2的区块的是否存在 +r($tester->block->getByID(3)) && p('id') && e('3'); // 测试ID为3的区块的是否存在 +r($tester->block->deleteBlock(0, 'project', 'statistic')) && p('') && e('1'); // 测试根据代号删除区块后的返回结果 +r($tester->block->getByID(3)) && p('id') && e('0'); // 测试ID为3的区块的是否存在 r($tester->block->deleteBlock(22)) && p('') && e('1'); // 测试ID为22的区块删除后的返回结果 \ No newline at end of file diff --git a/module/block/test/model/getadminblocklist.php b/module/block/test/model/getadminblocklist.php new file mode 100644 index 0000000000..e9b3a783eb --- /dev/null +++ b/module/block/test/model/getadminblocklist.php @@ -0,0 +1,42 @@ +#!/usr/bin/env php +id->range('2-5'); + $block->account->range('system'); + $block->vision->range('rnd'); + $block->dashboard->range('zentao'); + $block->module->range('zentao'); + $block->code->range('plugin,patch,publicclass,news'); + $block->createdDate->range('`2020-01-01`,`2021-01-01`'); + $block->title->prefix('区块')->range('2-5'); + $block->order->range('2-5'); + + $block->gen(4); +} + +/** + +title=测试 block 模块 model下的 getMyDashboard 方法 +timeout=0 +cid=39 + +- 查询admin用户在我的地盘仪表盘的区块列表数量 @4 + +- 查询admin用户在我的地盘仪表盘的区块列表数量 @2 + +*/ + +global $tester; +$tester->loadModel('block'); + +initData(); + +r(count($tester->block->getAdminBlockList())) && p('') && e('4'); // 查询admin用户在我的地盘仪表盘的区块列表数量 +r(count($tester->block->getAdminBlockList('2021-01-01'))) && p('') && e('2'); // 查询admin用户在我的地盘仪表盘的区块列表数量 \ No newline at end of file diff --git a/module/block/test/model/getblockinitstatus.php b/module/block/test/model/getblockinitstatus.php index 8241221aed..a73782de0a 100644 --- a/module/block/test/model/getblockinitstatus.php +++ b/module/block/test/model/getblockinitstatus.php @@ -22,6 +22,12 @@ title=测试 blockModel->getBlockInitStatus(); timeout=0 cid=1 +- 测试 my 模块下返回为 1 @1 + +- 测试 product 模块下返回为 0 @0 + +- 测试不存在模块返回为 0 @0 + */ global $tester; @@ -31,4 +37,4 @@ initData(); r($tester->block->getBlockInitStatus('my')) && p('') && e('1'); // 测试 my 模块下返回为 1 r($tester->block->getBlockInitStatus('product')) && p('') && e('0'); // 测试 product 模块下返回为 0 -r($tester->block->getBlockInitStatus('asda??')) && p('') && e('0'); // 测试不存在模块返回为 0 +r($tester->block->getBlockInitStatus('asda??')) && p('') && e('0'); // 测试不存在模块返回为 0 \ No newline at end of file diff --git a/module/block/test/model/getbyid.php b/module/block/test/model/getbyid.php index b1bbbba46a..b320b62cf8 100755 --- a/module/block/test/model/getbyid.php +++ b/module/block/test/model/getbyid.php @@ -1,6 +1,6 @@ #!/usr/bin/env php loadModel('block'); initData(); -r($tester->block->getByID(2)) && p('account,vision,dashboard,module,code,title,order') && e('admin,rnd,my,project,list,区块名称2,1'); // 测试获取正常的block的内容 -r($tester->block->getByID(4)) && p('order') && e('3'); // 测试获取正常的block的内容 -r($tester->block->getByID(6)) && p('') && e('0'); // 测试获取不存在的block的内容 \ No newline at end of file +r($tester->block->getByID(2)) && p('account,vision,dashboard,module,code,title,order') && e('admin,rnd,my,project,list,区块名称2,1'); // 测试获取正常的block的内容 +r($tester->block->getByID(4)) && p('order') && e('3'); // 测试获取正常的block的内容 +r($tester->block->getByID(6)) && p('') && e('0'); // 测试获取不存在的block的内容 \ No newline at end of file diff --git a/module/block/test/model/getclosedblockpairs.php b/module/block/test/model/getclosedblockpairs.php new file mode 100755 index 0000000000..c2d6f886ed --- /dev/null +++ b/module/block/test/model/getclosedblockpairs.php @@ -0,0 +1,23 @@ +#!/usr/bin/env php +getClosedBlockPairs(); +timeout=0 +cid=1 + +- 测试获取关闭的区块键值对属性product|list @产品|产品列表 + +- 测试获取关闭的区块键值对属性assigntome|assigntome @待处理 + +*/ + +global $tester; +$tester->loadModel('block'); +$data = $tester->block->getClosedBlockPairs('product|list,assigntome|assigntome'); +r($data) && p('product|list') && e('产品|产品列表'); //测试获取关闭的区块键值对 +r($data) && p('assigntome|assigntome') && e('待处理'); //测试获取关闭的区块键值对 \ No newline at end of file diff --git a/module/block/test/model/getmaxorderbydashboard.php b/module/block/test/model/getmaxorderbydashboard.php index 37642b47d5..dcb3212979 100644 --- a/module/block/test/model/getmaxorderbydashboard.php +++ b/module/block/test/model/getmaxorderbydashboard.php @@ -1,6 +1,6 @@ #!/usr/bin/env php loadModel('block'); initData(); -r($tester->block->getMaxOrderByDashboard('my')) && p('') && e('4'); // 测试 my 模块下最大order 为 4 -r($tester->block->getMaxOrderByDashboard('my')) && p('') && e('4'); // 测试 my 模块下最大order 为 4 +r($tester->block->getMaxOrderByDashboard('my')) && p('') && e('4'); // 测试 my 模块下最大order 为 4 +r($tester->block->getMaxOrderByDashboard('my')) && p('') && e('4'); // 测试 my 模块下最大order 为 4 r($tester->block->getMaxOrderByDashboard('bug')) && p('') && e('0'); // 测试数据库中不存在 模块的最大排序 为 0 \ No newline at end of file diff --git a/module/block/test/model/getmydashboard.php b/module/block/test/model/getmydashboard.php index 9d3e690b35..6bdf5933b9 100644 --- a/module/block/test/model/getmydashboard.php +++ b/module/block/test/model/getmydashboard.php @@ -1,6 +1,6 @@ #!/usr/bin/env php loadModel('block'); initData(); -r(count($tester->block->getMyDashboard('my'))) && p('') && e('1'); // 查询admin用户在我的地盘仪表盘的区块列表数量 -r($tester->block->getMyDashboard('my')) && p('2:title') && e('区块2'); // 查询admin用户在我的地盘仪表盘的区块标题第2条的title属性 -r(count($tester->block->getMyDashboard('my', 1))) && p('') && e('0'); // 查询admin用户在我的地盘仪表盘的隐藏区块列表数量 -r(count($tester->block->getMyDashboard('asdas?'))) && p('') && e('0'); // 查询admin用户在不存在仪表盘的区块列表数量 -r($tester->block->getMyDashboard('my')) && p('2:vision') && e('rnd'); // 查询admin用户在我的地盘仪表盘区块的vision第2条的vision属性 \ No newline at end of file +r(count($tester->block->getMyDashboard('my'))) && p('') && e('1'); // 查询admin用户在我的地盘仪表盘的区块列表数量 +r($tester->block->getMyDashboard('my')) && p('2:title') && e('区块2'); // 查询admin用户在我的地盘仪表盘的区块标题第2条的title属性 +r(count($tester->block->getMyDashboard('my', 1))) && p('') && e('0'); // 查询admin用户在我的地盘仪表盘的隐藏区块列表数量 +r(count($tester->block->getMyDashboard('asdas?'))) && p('') && e('0'); // 查询admin用户在不存在仪表盘的区块列表数量 +r($tester->block->getMyDashboard('my')) && p('2:vision') && e('rnd'); // 查询admin用户在我的地盘仪表盘区块的vision第2条的vision属性 \ No newline at end of file diff --git a/module/block/test/model/getspecifiedblockid.php b/module/block/test/model/getspecifiedblockid.php index 4824c6437d..f5342ec4f6 100644 --- a/module/block/test/model/getspecifiedblockid.php +++ b/module/block/test/model/getspecifiedblockid.php @@ -1,6 +1,6 @@ #!/usr/bin/env php loadModel('block'); initData(); r($tester->block->getSpecifiedBlockID('my', 'assingtome', 'assingtome')) && p('') && e('2'); // 测试 assingtome 区块的 区块ID是否为2 -r($tester->block->getSpecifiedBlockID('qa', 'bug', 'list')) && p('') && e('3'); // 测试 buglist 区块的 区块ID是否为2 -r($tester->block->getSpecifiedBlockID('my', 'welcome', 'welcome')) && p('') && e('0'); // 测试 找不到的区块的 区块ID 是否为 false \ No newline at end of file +r($tester->block->getSpecifiedBlockID('qa', 'bug', 'list')) && p('') && e('3'); // 测试 buglist 区块的 区块ID是否为2 +r($tester->block->getSpecifiedBlockID('my', 'welcome', 'welcome')) && p('') && e('0'); // 测试 找不到的区块的 区块ID 是否为 false \ No newline at end of file diff --git a/module/block/test/model/initblock.php b/module/block/test/model/initblock.php index 28b1e2f6a9..090be20f6a 100755 --- a/module/block/test/model/initblock.php +++ b/module/block/test/model/initblock.php @@ -1,6 +1,6 @@ #!/usr/bin/env php initBlock(); timeout=0 cid=1 -- 测试获取正常的block的内容 +- 测试获取初始化后第一个的block的内容 - 属性account @admin - 属性dashboard @my - 属性module @welcome - 属性code @welcome - 属性title @欢迎 -- 测试获取正常的block的内容 +- 测试获取初始化后最后一个的block的内容 - 属性account @admin - 属性dashboard @my - 属性module @project @@ -33,5 +33,5 @@ $tester->block->reset('my'); $tester->block->initBlock('my'); $newBlcoks = $tester->block->getMyDashboard('my'); -r(reset($newBlcoks)) && p('account,dashboard,module,code,title') && e('admin,my,welcome,welcome,欢迎'); // 测试获取正常的block的内容 -r(end($newBlcoks)) && p('account,dashboard,module,code,title') && e('admin,my,project,project,项目列表'); // 测试获取正常的block的内容 \ No newline at end of file +r(reset($newBlcoks)) && p('account,dashboard,module,code,title') && e('admin,my,welcome,welcome,欢迎'); // 测试获取初始化后第一个的block的内容 +r(end($newBlcoks)) && p('account,dashboard,module,code,title') && e('admin,my,project,project,项目列表'); // 测试获取初始化后最后一个的block的内容 \ No newline at end of file diff --git a/module/block/test/model/islongblock.php b/module/block/test/model/islongblock.php index 7f6c32e76c..acf4db8874 100755 --- a/module/block/test/model/islongblock.php +++ b/module/block/test/model/islongblock.php @@ -1,6 +1,6 @@ #!/usr/bin/env php isLongBlock(); timeout=0 cid=1 -- 测试 空 对象 检查是否为长区块 @0 +- 测试空对象是否为长区块 @0 -- 测试 空 对象 检查是否为长区块 @0 +- 测试ID为2的对象是否为长区块 @0 -- 测试 空 对象 检查是否为长区块 @1 +- 测试ID为3的对象是否为长区块 @1 */ @@ -42,6 +42,6 @@ initData(); $emptyObject = new stdclass(); -r($tester->block->isLongBlock($emptyObject)) && p() && e('0'); // 测试 空 对象 检查是否为长区块 -r($tester->block->isLongBlock($tester->block->getByID(2))) && p() && e('0'); // 测试 空 对象 检查是否为长区块 -r($tester->block->isLongBlock($tester->block->getByID(3))) && p() && e('1'); // 测试 空 对象 检查是否为长区块 \ No newline at end of file +r($tester->block->isLongBlock($emptyObject)) && p() && e('0'); // 测试空对象是否为长区块 +r($tester->block->isLongBlock($tester->block->getByID(2))) && p() && e('0'); // 测试ID为2的对象是否为长区块 +r($tester->block->isLongBlock($tester->block->getByID(3))) && p() && e('1'); // 测试ID为3的对象是否为长区块 \ No newline at end of file diff --git a/module/block/test/model/reset.php b/module/block/test/model/reset.php index 6afe36b26f..d26fbdd4b6 100644 --- a/module/block/test/model/reset.php +++ b/module/block/test/model/reset.php @@ -1,6 +1,6 @@ #!/usr/bin/env php block->reset('my'); $tester->block->initBlock('my'); r($tester->block->getBlockInitStatus('my')) && p('') && e('1'); // 检查初始化前的区块初始化状态返回结果 -$myDashboard = $tester->block->getMyDashboard('my'); -r(reset($myDashboard)) && p('account,dashboard,module,code,title') && e('admin,my,welcome,welcome,欢迎'); // 检查初始化前是否存在区块数据 + +$resetBeforeBlocks = $tester->block->getMyDashboard('my'); +r(reset($resetBeforeBlocks)) && p('account,dashboard,module,code,title') && e('admin,my,welcome,welcome,欢迎'); // 检查初始化前是否存在区块数据 + r($tester->block->reset('my')) && p('') && e('1'); // 检查初始化后的返回结果 r($tester->block->getBlockInitStatus('my')) && p('') && e('0'); // 检查初始化后的区块初始化状态返回结果 -$myDashboard2 = $tester->block->getMyDashboard('my'); -r(reset($myDashboard2)) && p('account') && e('0'); // 检查初始化前是否存在区块数据 \ No newline at end of file + +$resetAfterBlocks = $tester->block->getMyDashboard('my'); +r(reset($resetAfterBlocks)) && p('account') && e('0'); // 检查初始化后是否存在区块数据 \ No newline at end of file diff --git a/module/block/test/model/setorder.php b/module/block/test/model/setorder.php index 64aafc8e00..9dd4759244 100644 --- a/module/block/test/model/setorder.php +++ b/module/block/test/model/setorder.php @@ -1,6 +1,6 @@ #!/usr/bin/env php order = '1'; $blockTest = new blockTest(); r($tester->block->setOrder($block1->id, $block1->order)) && p('') && e('1'); // 测试ID为2的区块变更排序后的返回结果 -r($tester->block->getByID($block1->id)) && p('id,order') && e("2,3"); // 测试ID为2的区块的序号 +r($tester->block->getByID($block1->id)) && p('id,order') && e('2,3'); // 测试ID为2的区块的序号 r($tester->block->setOrder($block2->id, $block2->order)) && p('') && e('1'); // 测试ID为2的区块变更排序后的返回结果 -r($tester->block->getByID($block2->id)) && p('id,order') && e("3,1"); // 测试ID为3的区块的序号 \ No newline at end of file +r($tester->block->getByID($block2->id)) && p('id,order') && e('3,1'); // 测试ID为3的区块的序号 \ No newline at end of file diff --git a/module/block/test/model/update.php b/module/block/test/model/update.php index abfbd75d78..57b98ecb38 100644 --- a/module/block/test/model/update.php +++ b/module/block/test/model/update.php @@ -1,6 +1,6 @@ #!/usr/bin/env php title = ''; $blockTest = new blockTest(); $newBlockID = $blockTest->updateTest($block); -r($tester->block->getByID($newBlockID)) && p('account,vision,dashboard,module,code,title,order') && e("admin2,lite,scrumproject,project,statistic,项目统计区块,1"); // 测试获取正常的block的内容 +r($tester->block->getByID($newBlockID)) && p('account,vision,dashboard,module,code,title,order') && e('admin2,lite,scrumproject,project,statistic,项目统计区块,1'); // 测试获取正常的block的内容 r($blockTest->updateTest($emptyModuleBlock)) && p('module:0') && e('『所属模块』不能为空。'); // 测试所属模块为空 r($blockTest->updateTest($emptyCodeBlock)) && p('code:0') && e('『区块』不能为空。'); // 测试所属区块为空 r($blockTest->updateTest($emptyTitleBlock)) && p('title:0') && e('『区块名称』不能为空。'); // 测试区块名称为空 diff --git a/module/block/ui/productstatisticblock.html.php b/module/block/ui/productstatisticblock.html.php index 6e2207cc4f..90d78f129f 100644 --- a/module/block/ui/productstatisticblock.html.php +++ b/module/block/ui/productstatisticblock.html.php @@ -23,7 +23,7 @@ namespace zin; function getProductTabs($products, $blockNavCode): array { $navTabs = array(); - $selected = key($products); + $selected = key($products); foreach($products as $product) { $navTabs[] = li @@ -65,7 +65,7 @@ function getProductInfo($products, $blockNavID): array { global $lang; - $selected = key($products); + $selected = key($products); $tabItems = array(); foreach($products as $product) { diff --git a/module/block/ui/projectstatisticblock.html.php b/module/block/ui/projectstatisticblock.html.php index 01e7b99a49..6cb03fd933 100644 --- a/module/block/ui/projectstatisticblock.html.php +++ b/module/block/ui/projectstatisticblock.html.php @@ -23,7 +23,7 @@ namespace zin; function getProjectTabs($projects, $blockNavCode): array { $navTabs = array(); - $selected = key($projects); + $selected = key($projects); foreach($projects as $project) { $navTabs[] = li @@ -63,7 +63,7 @@ function getProjectTabs($projects, $blockNavCode): array */ function getProjectInfo($projects, $blockNavID): array { - $selected = key($projects); + $selected = key($projects); $tabItems = array(); foreach($projects as $project) { diff --git a/module/block/ui/qastatisticblock.html.php b/module/block/ui/qastatisticblock.html.php index 4dd1edd378..51b090d2a0 100644 --- a/module/block/ui/qastatisticblock.html.php +++ b/module/block/ui/qastatisticblock.html.php @@ -23,7 +23,7 @@ namespace zin; function getProductTabs($products, $blockNavCode): array { $navTabs = array(); - $selected = key($products); + $selected = key($products); foreach($products as $product) { $navTabs[] = li @@ -65,7 +65,7 @@ function getProductInfo($products, $blockNavID): array { global $lang; - $selected = key($products); + $selected = key($products); $tabItems = array(); foreach($products as $product) { diff --git a/module/block/zen.php b/module/block/zen.php index ab5c23d37b..ef08d882e5 100644 --- a/module/block/zen.php +++ b/module/block/zen.php @@ -16,9 +16,13 @@ class blockZen extends block if($dashboard != 'my') return array(); $modules = $this->lang->block->moduleList; + /* Unable to display the doc module on my dashboard. */ unset($modules['doc']); + if($this->config->global->flow != 'full') unset($modules['guide']); + if($this->config->vision != 'rnd') unset($modules['contribute']); + /* 从配置项中取出不同模块的首页对应的控制器方法。*/ /* Retrieve the controller method corresponding to the homepage of different modules from the configuration item.*/ list($programModule, $programMethod) = explode('-', $this->config->programLink); @@ -26,6 +30,7 @@ class blockZen extends block list($projectModule, $projectMethod) = explode('-', $this->config->projectLink); list($executionModule, $executionMethod) = explode('-', $this->config->executionLink); + $closedBlock = isset($this->config->block->closed) ? $this->config->block->closed : ''; foreach($modules as $moduleKey => $moduleName) { if($moduleKey == 'todo') continue; @@ -40,17 +45,11 @@ class blockZen extends block /* After obtaining module permissions, it is necessary to verify whether there is permission for the module homepage. */ if(!common::hasPriv($moduleKey, $method)) unset($modules[$moduleKey]); - } - /* 判断区块是否被永久关闭,如果未被永久关闭则添加相应选项。*/ - /* Determine whether the block has been permanently closed, and add corresponding options if it has not been permanently closed.*/ - $closedBlock = isset($this->config->block->closed) ? $this->config->block->closed : ''; - if(strpos(",$closedBlock,", ",|assigntome,") === false) $modules['assigntome'] = $this->lang->block->assignToMe; - if(strpos(",$closedBlock,", ",|dynamic,") === false) $modules['dynamic'] = $this->lang->block->dynamic; - if(strpos(",$closedBlock,", ",|guide,") === false and $this->config->global->flow == 'full') $modules['guide'] = $this->lang->block->guide; - if(strpos(",$closedBlock,", ",|welcome,") === false and $this->config->global->flow == 'full') $modules['welcome'] = $this->lang->block->welcome; - if(strpos(",$closedBlock,", ",|html,") === false) $modules['html'] = 'HTML'; - if(strpos(",$closedBlock,", ",|contribute,") === false and $this->config->vision == 'rnd') $modules['contribute'] = $this->lang->block->contribute; + /* 被永久关闭的区块删除对应选项。 */ + /* Delete corresponding options for blocks that have been permanently closed. */ + if(strpos(",$closedBlock,", ",$moduleKey|$moduleKey,") !== false) unset($modules[$moduleKey]); + } return array('' => '') + $modules; } diff --git a/module/programplan/model.php b/module/programplan/model.php index d27f174dd5..faf0bff7ad 100755 --- a/module/programplan/model.php +++ b/module/programplan/model.php @@ -100,7 +100,8 @@ class programplanModel extends model } /** - * Get pairs. + * 获取项目中的阶段数据键值对。 + * Get plan pairs in the project. * * @param int $executionID * @param int $productID @@ -108,7 +109,7 @@ class programplanModel extends model * @access public * @return array */ - public function getPairs($executionID, $productID = 0, $type = 'all') + public function getPairs(int $executionID, int $productID = 0, string $type = 'all'): array { $plans = $this->getStage($executionID, $productID, $type); @@ -124,12 +125,14 @@ class programplanModel extends model { if(strpos($type, 'leaf') !== false and isset($parents[$plan->id])) continue; - $paths = array_slice(explode(',', trim($plan->path, ',')), 1); + $paths = array_slice(explode(',', trim($plan->path, ',')), 1); $planName = ''; + foreach($paths as $path) { if(isset($plans[$path])) $planName .= '/' . $plans[$path]->name; } + $pairs[$planID] = $planName; } @@ -155,34 +158,176 @@ class programplanModel extends model $plans = $this->getStage($executionID, $productID, 'all', 'order'); - if($baselineID) + if($baselineID) $plans = $this->setBaseline($baselineID, $plans); + + $today = helper::today(); + $datas = array(); + $planIdList = array(); + $isMilestone = " "; + $stageIndex = array(); + foreach($plans as $plan) { - /* Set plan baseline. */ - $baseline = $this->loadModel('cm')->getByID($baselineID); - $oldData = json_decode($baseline->data); - $oldPlans = $oldData->stage; - $plans = $this->programplanTao->setPlanBaseline($oldPlans, $plans); + $planIdList[$plan->id] = $plan->id; + + $start = helper::isZeroDate($plan->begin) ? '' : $plan->begin; + $end = helper::isZeroDate($plan->end) ? '' : $plan->end; + $realBegan = helper::isZeroDate($plan->realBegan) ? '' : $plan->realBegan; + $realEnd = helper::isZeroDate($plan->realEnd) ? '' : $plan->realEnd; + + $data = new stdclass(); + $data->id = $plan->id; + $data->type = 'plan'; + $data->text = empty($plan->milestone) ? $plan->name : $plan->name . $isMilestone ; + $data->name = $plan->name; + if(isset($this->config->setPercent) and $this->config->setPercent == 1) $data->percent = $plan->percent; + $data->attribute = zget($this->lang->stage->typeList, $plan->attribute); + $data->milestone = zget($this->lang->programplan->milestoneList, $plan->milestone); + $data->owner_id = $plan->PM; + $data->status = $this->processStatus('execution', $plan); + $data->begin = $start; + $data->deadline = $end; + $data->realBegan = $realBegan ? substr($realBegan, 0, 10) : ''; + $data->realEnd = $realEnd ? substr($realEnd, 0, 10) : '';; + $data->parent = $plan->grade == 1 ? 0 :$plan->parent; + $data->open = true; + $data->start_date = $realBegan ? $realBegan : $start; + $data->endDate = $realEnd ? $realEnd : $end; + $data->duration = 1; + $data->color = $this->lang->execution->gantt->stage->color; + $data->progressColor = $this->lang->execution->gantt->stage->progressColor; + $data->textColor = $this->lang->execution->gantt->stage->textColor; + $data->bar_height = $this->lang->execution->gantt->bar_height; + + /* Determines if the object is delay. */ + $data->delay = $this->lang->programplan->delayList[0]; + $data->delayDays = 0; + if($today > $end) + { + $data->delay = $this->lang->programplan->delayList[1]; + $data->delayDays = helper::diffDate($today, substr($end, 0, 10)); + } + + if($data->endDate > $data->start_date) $data->duration = helper::diffDate(substr($data->endDate, 0, 10), substr($data->start_date, 0, 10)) + 1; + if($data->start_date) $data->start_date = date('d-m-Y', strtotime($data->start_date)); + if($data->start_date == '' or $data->endDate == '') $data->duration = 1; + + $datas['data'][$plan->id] = $data; + $stageIndex[$plan->id] = array('planID' => $plan->id, 'parent' => $plan->parent, 'progress' => array('totalEstimate' => 0, 'totalConsumed' => 0, 'totalReal' => 0)); } - $datas = $planIdList = $stageIndex = array(); - - /* Set plan for gantt view. */ - $this->programplanTao->setPlan($plans, $datas, $stageIndex, $planIdList); + $taskPri = "%s "; /* Judge whether to display tasks under the stage. */ - $owner = $this->app->user->account; - if(empty($selectCustom)) $selectCustom = $this->loadModel('setting')->getItem("owner={$owner}&module=programplan§ion=browse&key=stageCustom"); + $owner = $this->app->user->account; + $module = 'programplan'; + $section = 'browse'; + $object = 'stageCustom'; - $tasks = $this->dao->select('*')->from(TABLE_TASK)->where('deleted')->eq(0)->andWhere('execution')->in($planIdList)->orderBy('execution_asc, order_asc, id_desc')->fetchAll('id'); + if(empty($selectCustom)) $selectCustom = $this->loadModel('setting')->getItem("owner={$owner}&module={$module}§ion={$section}&key={$object}"); + + $tasks = $this->dao->select('*')->from(TABLE_TASK)->where('deleted')->eq(0)->andWhere('execution')->in($planIdList)->orderBy('execution_asc, order_asc, id_desc')->fetchAll('id'); + $taskTeams = $this->dao->select('task,account')->from(TABLE_TASKTEAM)->where('task')->in(array_keys($tasks))->fetchGroup('task', 'account'); + $users = $this->loadModel('user')->getPairs('noletter'); if($baselineID) { $oldTasks = $oldData->task; - $this->programplanTao->setTaskBaseline($oldTasks, $tasks); // Set task baseline. + foreach($oldTasks as $id => $oldTask) + { + if(!isset($tasks[$id])) continue; + $tasks[$id]->version = $oldTask->version; + $tasks[$id]->name = $oldTask->name; + $tasks[$id]->estStarted = $oldTask->estStarted; + $tasks[$id]->deadline = $oldTask->deadline; + } } - /* Set task for gantt view. */ - $this->programplanTao->setTask($tasks, $plans, $selectCustom, $datas, $stageIndex); + foreach($tasks as $task) + { + $execution = zget($plans, $task->execution, array()); + $priIcon = sprintf($taskPri, $task->pri, $task->pri, $task->pri); + + $estStart = helper::isZeroDate($task->estStarted) ? '' : $task->estStarted; + $estEnd = helper::isZeroDate($task->deadline) ? '' : $task->deadline; + $realBegan = helper::isZeroDate($task->realStarted) ? '' : $task->realStarted; + $realEnd = (in_array($task->status, array('done', 'closed')) and !helper::isZeroDate($task->finishedDate)) ? $task->finishedDate : ''; + + $start = $realBegan ? $realBegan : $estStart; + $end = $realEnd ? $realEnd : $estEnd; + if(empty($start) and $execution) $start = $execution->begin; + if(empty($end) and $execution) $end = $execution->end; + if($start > $end) $end = $start; + + $data = new stdclass(); + $data->id = $task->execution . '-' . $task->id; + $data->type = 'task'; + $data->text = $priIcon . $task->name; + $data->percent = ''; + $data->status = $this->processStatus('task', $task); + $data->owner_id = $task->assignedTo; + $data->attribute = ''; + $data->milestone = ''; + $data->begin = $start; + $data->deadline = $end; + $data->realBegan = $realBegan ? substr($realBegan, 0, 10) : ''; + $data->realEnd = $realEnd ? substr($realEnd, 0, 10) : ''; + $data->pri = $task->pri; + $data->parent = $task->parent > 0 ? $task->execution . '-' . $task->parent : $task->execution; + $data->open = true; + $progress = $task->consumed ? round($task->consumed / ($task->left + $task->consumed), 3) : 0; + $data->progress = $progress; + $data->taskProgress = ($progress * 100) . '%'; + $data->start_date = $start; + $data->endDate = $end; + $data->duration = 1; + $data->estimate = $task->estimate; + $data->consumed = $task->consumed; + $data->color = zget($this->lang->execution->gantt->color, $task->pri, $this->lang->execution->gantt->defaultColor); + $data->progressColor = zget($this->lang->execution->gantt->progressColor, $task->pri, $this->lang->execution->gantt->defaultProgressColor); + $data->textColor = zget($this->lang->execution->gantt->textColor, $task->pri, $this->lang->execution->gantt->defaultTextColor); + $data->bar_height = $this->lang->execution->gantt->bar_height; + + /* Determines if the object is delay. */ + $data->delay = $this->lang->programplan->delayList[0]; + $data->delayDays = 0; + if($today > $end) + { + $data->delay = $this->lang->programplan->delayList[1]; + $data->delayDays = helper::diffDate($today, substr($end, 0, 10)); + } + + /* If multi task then show the teams. */ + if($task->mode == 'multi' and !empty($taskTeams[$task->id])) + { + $teams = array_keys($taskTeams[$task->id]); + $assigneds = array(); + foreach($teams as $assignedTo) $assigneds[] = zget($users, $assignedTo); + $data->owner_id = join(',', $assigneds); + } + + if($data->endDate > $data->start_date) $data->duration = helper::diffDate(substr($data->endDate, 0, 10), substr($data->start_date, 0, 10)) + 1; + if($data->start_date) $data->start_date = date('d-m-Y', strtotime($data->start_date)); + if($data->start_date == '' or $data->endDate == '') $data->duration = 1; + + if(strpos($selectCustom, 'task') !== false) $datas['data'][$data->id] = $data; + foreach($stageIndex as $index => $stage) + { + if($stage['planID'] == $task->execution) + { + $stageIndex[$index]['progress']['totalEstimate'] += $task->estimate; + $stageIndex[$index]['progress']['totalConsumed'] += $task->parent == '-1' ? 0 : $task->consumed; + $stageIndex[$index]['progress']['totalReal'] += ($task->left + $task->consumed); + + $parent = $stage['parent']; + if(isset($stageIndex[$parent])) + { + $stageIndex[$parent]['progress']['totalEstimate'] += $task->estimate; + $stageIndex[$parent]['progress']['totalConsumed'] += $task->parent == '-1' ? 0 : $task->consumed; + $stageIndex[$parent]['progress']['totalReal'] += ($task->left + $task->consumed); + } + } + } + } /* Calculate the progress of the phase. */ foreach($stageIndex as $index => $stage) @@ -196,12 +341,50 @@ class programplanModel extends model $datas['data'][$index]->consumed = $stage['progress']['totalConsumed']; } - $datas = $this->setRelationTask($planIdList, $datas); + $datas['links'] = array(); + if($this->config->edition != 'open') + { + $relations = $this->dao->select('*')->from(TABLE_RELATIONOFTASKS)->where('execution')->in($planIdList)->orderBy('task,pretask')->fetchAll(); + foreach($relations as $relation) + { + $link['source'] = $relation->execution . '-' . $relation->pretask; + $link['target'] = $relation->execution . '-' . $relation->task; + $link['type'] = $this->config->execution->gantt->linkType[$relation->condition][$relation->action]; + $datas['links'][] = $link; + } + } $datas['data'] = isset($datas['data']) ? array_values($datas['data']) : array(); return $returnJson ? json_encode($datas) : $datas; } + /** + * 获取基线数据。 + * Get baseline data. + * + * @param int $baselineID + * @param array $plans + * @access private + * @return array + */ + private function setBaseline(int $baselineID, array $plans): array + { + $baseline = $this->loadModel('cm')->getByID($baselineID); + $oldData = json_decode($baseline->data); + $oldPlans = $oldData->stage; + foreach($oldPlans as $id => $oldPlan) + { + if(!isset($plans[$id])) continue; + $plans[$id]->version = $oldPlan->version; + $plans[$id]->name = $oldPlan->name; + $plans[$id]->milestone = $oldPlan->milestone; + $plans[$id]->begin = $oldPlan->begin; + $plans[$id]->end = $oldPlan->end; + } + + return $plans; + } + /** * Get gantt data group by assigned. * @@ -1070,8 +1253,6 @@ class programplanModel extends model common::printIcon('execution', 'delete', "planID=$plan->id&confirm=no", $plan, 'list', 'trash', 'hiddenwin' , $disabled, '', '', $this->lang->programplan->delete); } break; - default: - break; } echo ''; } @@ -1365,14 +1546,13 @@ class programplanModel extends model } /** - * 检查是否为顶级。 * Check whether it is the top stage. * * @param int $planID * @access public * @return bool */ - public function checkTopStage(int $planID): bool + public function checkTopStage($planID) { $parentID = $this->dao->select('parent')->from(TABLE_EXECUTION)->where('id')->eq($planID)->fetch('parent'); $parentType = $this->dao->select('type')->from(TABLE_EXECUTION)->where('id')->eq($parentID)->fetch('type'); diff --git a/module/programplan/test/model/yaml/getpairs/getpairs.yaml b/module/programplan/test/model/yaml/getpairs/getpairs.yaml new file mode 100644 index 0000000000..ff22cd7204 --- /dev/null +++ b/module/programplan/test/model/yaml/getpairs/getpairs.yaml @@ -0,0 +1,39 @@ +title: zt_getpairs +author: shenshulong +version: '1.0' +fields: + - field: id + range: 11-20 + - field: project + range: 11-15 + - field: name + range: 11-20 + prefix: '项目' + - field: code + range: 11-20 + prefix: 'project' + - field: model + range: 'scrum,waterfallplus' + - field: parent + range: 0-3 + - field: milestone + range: 0-1 + - field: auth + range: '[]' + - field: type + range: 'stage,project' + - field: grade + range: 1 + - field: days + range: 1 + - field: status + range: 'wait' + - field: desc + range: 11-20 + prefix: '项目描述' + - field: budget + range: '100000,200000' + - field: budgetUnit + range: 'CNY' + - field: percent + range: '0-0' diff --git a/module/programplan/test/model/yaml/getpairs/getprojectproduct.yaml b/module/programplan/test/model/yaml/getpairs/getprojectproduct.yaml new file mode 100644 index 0000000000..8693087b04 --- /dev/null +++ b/module/programplan/test/model/yaml/getpairs/getprojectproduct.yaml @@ -0,0 +1,13 @@ +title: zt_getprojectproduct +author: shenshulong +version: '1.0' +fields: + - field: project + range: 11-15 + - field: product + range: 11-15 + - field: branch + range: 31-49 + - field: plan + range: 11-15 + prefix: '计划'