This commit is contained in:
reneeteng
2020-03-13 15:37:13 +08:00
37 changed files with 535 additions and 63 deletions
+1
View File
@@ -23,6 +23,7 @@ common:
cp -fr framework zentaopms/
cp -fr lib zentaopms/
cp -fr module zentaopms/
cp -fr sdk zentaopms/
cp -fr www zentaopms && rm -fr zentaopms/www/data/ && mkdir -p zentaopms/www/data/upload
mkdir zentaopms/tmp
mkdir zentaopms/tmp/cache/
+1
View File
@@ -638,6 +638,7 @@ CREATE TABLE IF NOT EXISTS `zt_repo` (
`acl` text NOT NULL,
`synced` tinyint(1) NOT NULL DEFAULT '0',
`lastSync` datetime NOT NULL,
`desc` text NOT NULL,
`deleted` tinyint(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+2
View File
@@ -66,3 +66,5 @@ UPDATE `zt_webhook` SET `type` = 'wechatgroup' WHERE `type` = 'weixin';
UPDATE `zt_grouppriv` SET `method` = 'edit' WHERE `module` = 'repo' AND `method` = 'settings';
UPDATE `zt_grouppriv` SET `method` = 'showsynccommit' WHERE `module` = 'repo' AND `method` = 'showsynccomment';
UPDATE `zt_grouppriv` SET `method` = 'showsynccommit' WHERE `module` = 'repo' AND `method` = 'showSyncComment';
ALTER TABLE `zt_repo` ADD `desc` text NOT NULL AFTER `lastSync`;
+17
View File
@@ -0,0 +1,17 @@
ALTER TABLE `zt_story` ADD `parent` mediumint NOT NULL DEFAULT '0' AFTER `id`;
CREATE TABLE `zt_relation` (
`id` int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`program` mediumint(8) NOT NULL,
`product` mediumint(8) NOT NULL,
`project` mediumint(8) NOT NULL,
`AType` char(30) NOT NULL,
`AID` mediumint(8) NOT NULL,
`AVersion` char(30) NOT NULL,
`relation` char(30) NOT NULL,
`BType` char(30) NOT NULL,
`BID` mediumint(8) NOT NULL,
`BVersion` char(30) NOT NULL,
`extra` char(30) NOT NULL,
UNIQUE KEY `relation` (`relation`,`AType`,`BType`, `AID`, `BID`)
) ENGINE='MyISAM' DEFAULT CHARSET=utf8;
+1
View File
@@ -641,6 +641,7 @@ CREATE TABLE IF NOT EXISTS `zt_repo` (
`acl` text NOT NULL,
`synced` tinyint(1) NOT NULL DEFAULT '0',
`lastSync` datetime NOT NULL,
`desc` text NOT NULL,
`deleted` tinyint(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+19 -1
View File
@@ -133,12 +133,19 @@ $lang->action->desc->linked2bug = '$date 由 <strong>$actor</strong> 关联
/* 用来描述和父子任务相关的操作历史记录。*/
$lang->action->desc->createchildren = '$date, 由 <strong>$actor</strong> 创建子任务 <strong>$extra</strong>。' . "\n";
$lang->action->desc->linkchildtask = '$date, 由 <strong>$actor</strong> 关联子任务 <strong>$extra</strong>。' . "\n";
$lang->action->desc->linkchildtask = '$date, 由 <strong>$actor</strong> 关联子任务 <strong>$extra</strong>。' . "\n";
$lang->action->desc->unlinkchildrentask = '$date, 由 <strong>$actor</strong> 移除子任务 <strong>$extra</strong>。' . "\n";
$lang->action->desc->linkparenttask = '$date, 由 <strong>$actor</strong> 关联到父任务 <strong>$extra</strong>。' . "\n";
$lang->action->desc->unlinkparenttask = '$date, 由 <strong>$actor</strong> 从父任务<strong>$extra</strong>取消关联。' . "\n";
$lang->action->desc->deletechildrentask = '$date, 由 <strong>$actor</strong> 删除子任务<strong>$extra</strong>。' . "\n";
/* 用来描述和父子需求相关的操作历史记录。*/
$lang->action->desc->createchildrenstory = '$date, 由 <strong>$actor</strong> 创建子需求 <strong>$extra</strong>。' . "\n";
$lang->action->desc->linkchildstory = '$date, 由 <strong>$actor</strong> 关联子需求 <strong>$extra</strong>。' . "\n";
$lang->action->desc->unlinkchildrenstory = '$date, 由 <strong>$actor</strong> 移除子需求 <strong>$extra</strong>。' . "\n";
$lang->action->desc->linkparentstory = '$date, 由 <strong>$actor</strong> 关联到父需求 <strong>$extra</strong>。' . "\n";
$lang->action->desc->unlinkparentstory = '$date, 由 <strong>$actor</strong> 从父需求<strong>$extra</strong>取消关联。' . "\n";
$lang->action->desc->deletechildrenstory = '$date, 由 <strong>$actor</strong> 删除子需求<strong>$extra</strong>。' . "\n";
/* 关联用例和移除用例时的历史操作记录。*/
$lang->action->desc->linkrelatedcase = '$date, 由 <strong>$actor</strong> 关联相关用例 <strong>$extra</strong>。' . "\n";
$lang->action->desc->unlinkrelatedcase = '$date, 由 <strong>$actor</strong> 移除相关用例 <strong>$extra</strong>。' . "\n";
@@ -211,6 +218,12 @@ $lang->action->label->batchcreate = "批量创建任务";
$lang->action->label->createchildren = "创建子任务";
$lang->action->label->managed = "维护";
$lang->action->label->deletechildrentask = "删除子任务";
$lang->action->label->createchildrenstory = "创建子需求";
$lang->action->label->linkchildstory = "关联子需求";
$lang->action->label->unlinkchildrenstory = "取消关联子需求";
$lang->action->label->linkparentstory = "关联到父需求";
$lang->action->label->unlinkparentstory = "从父需求取消关联";
$lang->action->label->deletechildrenstory = "删除子需求";
/* 动态信息按照对象分组 */
$lang->action->dynamicAction = new stdclass();
@@ -286,6 +299,11 @@ $lang->action->dynamicAction->task['unlinkparenttask'] = '从父任务取消
$lang->action->dynamicAction->task['deletechildrentask'] = '删除子任务';
$lang->action->dynamicAction->task['linkparenttask'] = '关联到父任务';
$lang->action->dynamicAction->task['linkchildtask'] = '关联子任务';
$lang->action->dynamicAction->task['createchildrenstory'] = '创建子需求';
$lang->action->dynamicAction->task['unlinkparentstory'] = '从父需求取消关联';
$lang->action->dynamicAction->task['deletechildrenstory'] = '删除子需求';
$lang->action->dynamicAction->task['linkparentstory'] = '关联到父需求';
$lang->action->dynamicAction->task['linkchildstory'] = '关联子需求';
$lang->action->dynamicAction->task['undeleted'] = '还原任务';
$lang->action->dynamicAction->task['hidden'] = '隐藏任务';
$lang->action->dynamicAction->task['svncommited'] = 'SVN提交';
+5
View File
@@ -319,6 +319,11 @@ class actionModel extends model
$name = $this->dao->select('name')->from(TABLE_TASK)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('task', 'view') ? html::a(helper::createLink('task', 'view', "taskID=$action->extra"), "#$action->extra " . $name) : "#$action->extra " . $name;
}
elseif($actionName == 'linkchildstory' or $actionName == 'unlinkchildrenstory' or $actionName == 'linkparentstory' or $actionName == 'unlinkparentstory' or $actionName == 'deletechildrenstory')
{
$name = $this->dao->select('title')->from(TABLE_STORY)->where('id')->eq($action->extra)->fetch('title');
if($name) $action->extra = common::hasPriv('story', 'view') ? html::a(helper::createLink('story', 'view', "storyID=$action->extra"), "#$action->extra " . $name) : "#$action->extra " . $name;
}
elseif($actionName == 'buildopened')
{
$name = $this->dao->select('name')->from(TABLE_BUILD)->where('id')->eq($action->objectID)->fetch('name');
+6 -1
View File
@@ -46,7 +46,12 @@
<?php echo html::checkbox('stories', array($story->id => sprintf('%03d', $story->id)), ($story->stage == 'developed' or $story->status == 'closed') ? $story->id : '');?>
</td>
<td><span class='label-pri label-pri-<?php echo $story->pri;?>' title='<?php echo zget($lang->story->priList, $story->pri, $story->pri);?>'><?php echo zget($lang->story->priList, $story->pri, $story->pri)?></span></td>
<td class='text-left nobr' title='<?php echo $story->title?>'><?php echo html::a($this->createLink('story', 'view', "storyID=$story->id", '', true), $story->title, '', "data-toggle='modal' data-type='iframe' data-width='90%'");?></td>
<td class='text-left nobr' title='<?php echo $story->title?>'>
<?php
if($story->parent > 0) echo "<span class='label'>{$lang->story->childrenAB}</span>";
echo html::a($this->createLink('story', 'view', "storyID=$story->id", '', true), $story->title, '', "data-toggle='modal' data-type='iframe' data-width='90%'");
?>
</td>
<td><?php echo zget($users, $story->openedBy);?></td>
<td><?php echo zget($users, $story->assignedTo);?></td>
<td><?php echo $story->estimate;?></td>
+6 -1
View File
@@ -130,7 +130,12 @@ tbody tr td:first-child input{display:none;}
<?php endif;?>
</td>
<td><span class='label-pri label-pri-<?php echo $story->pri;?>' title='<?php echo zget($lang->story->priList, $story->pri, $story->pri);?>'><?php echo zget($lang->story->priList, $story->pri, $story->pri);?></span></td>
<td class='text-left nobr' title='<?php echo $story->title?>'><?php echo html::a($storyLink,$story->title, '', "class='iframe' data-width='1000'");?></td>
<td class='text-left nobr' title='<?php echo $story->title?>'>
<?php
if($story->parent > 0) echo "<span class='label'>{$lang->story->childrenAB}</span>";
echo html::a($storyLink,$story->title, '', "class='iframe' data-width='1000'");
?>
</td>
<td><?php echo zget($users, $story->openedBy);?></td>
<td><?php echo $story->estimate;?></td>
<td>
+8 -1
View File
@@ -153,7 +153,14 @@ class product extends control
/* Get related tasks, bugs, cases count of each story. */
$storyIdList = array();
foreach($stories as $story) $storyIdList[$story->id] = $story->id;
foreach($stories as $story)
{
$storyIdList[$story->id] = $story->id;
if(!empty($story->children))
{
foreach($story->children as $child) $storyIdList[$child->id] = $child->id;
}
}
$storyTasks = $this->loadModel('task')->getStoryTaskCounts($storyIdList);
$storyBugs = $this->loadModel('bug')->getStoryBugCounts($storyIdList);
$storyCases = $this->loadModel('testcase')->getStoryCaseCounts($storyIdList);
+12
View File
@@ -15,3 +15,15 @@
.popoverStage .icon-caret-down{position:absolute;}
a.removeModule{color:#ddd}
a.removeModule:hover{color:red}
.table-children {border-left: 2px solid #cbd0db; border-right: 2px solid #cbd0db;}
.table tbody > tr.table-children.table-child-top {border-top: 2px solid #cbd0db;}
.table tbody > tr.table-children.table-child-bottom {border-bottom: 2px solid #cbd0db;}
.table td.has-child > a:not(.story-toggle) {max-width: 90%; max-width: calc(100% - 30px); display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
.table td.has-child > .story-toggle {color: #838a9d; position: relative; top: 1px;}
.table td.has-child > .story-toggle:hover {color: #006af1; cursor: pointer;}
.table td.has-child > .story-toggle > .icon {font-size: 16px; display: inline-block; transition: transform .2s; -ms-transform:rotate(-90deg); -moz-transform:rotate(-90deg); -o-transform:rotate(-90deg); -webkit-transform:rotate(-90deg); transform: rotate(-90deg);}
.table td.has-child > .story-toggle > .icon:before {text-align: left;}
.table td.has-child > .story-toggle.collapsed > .icon {-ms-transform:rotate(90deg); -moz-transform:rotate(90deg); -o-transform:rotate(90deg); -webkit-transform:rotate(90deg); transform: rotate(90deg);}
.main-table tbody > tr.table-children > td:first-child::before {width: 3px;}
@-moz-document url-prefix() {.main-table tbody > tr.table-children > td:first-child::before {width: 4px;}}
+10
View File
@@ -1,6 +1,16 @@
$(function()
{
if($('#storyList thead th.c-title').width() < 150) $('#storyList thead th.c-title').width(150);
$(document).on('click', '.story-toggle', function(e)
{
var $toggle = $(this);
var id = $(this).data('id');
var isCollapsed = $toggle.toggleClass('collapsed').hasClass('collapsed');
$toggle.closest('[data-ride="table"]').find('tr.parent-' + id).toggle(!isCollapsed);
e.stopPropagation();
e.preventDefault();
});
// Fix state dropdown menu position
$('.c-stage > .dropdown').each(function()
+1
View File
@@ -584,6 +584,7 @@ class productModel extends model
if($browseType == 'changedstory') $stories = $this->story->getByStatus($productID, $branch, $modules, 'changed', $type, $sort, $pager);
if($browseType == 'willclose') $stories = $this->story->get2BeClosed($productID, $branch, $modules, $type, $sort, $pager);
if($browseType == 'closedstory') $stories = $this->story->getByStatus($productID, $branch, $modules, 'closed', $type, $sort, $pager);
if($browseType == 'emptysr') $stories = $this->story->getEmptySR($productID, $branch, $modules, '', $type, $sort, $pager);
return $stories;
}
+12
View File
@@ -33,6 +33,7 @@
foreach(customModel::getFeatureMenu($this->moduleName, $this->methodName) as $menuItem)
{
if(isset($menuItem->hidden)) continue;
if($menuItem->name == 'emptysr' && $storyType == 'story') continue;
$menuBrowseType = strpos($menuItem->name, 'QUERY') === 0 ? 'bySearch' : $menuItem->name;
if($menuItem->name == 'more')
{
@@ -198,6 +199,17 @@
<tr data-id='<?php echo $story->id?>' data-estimate='<?php echo $story->estimate?>' data-cases='<?php echo zget($storyCases, $story->id, 0);?>'>
<?php foreach($setting as $key => $value) $this->story->printCell($value, $story, $users, $branches, $storyStages, $modulePairs, $storyTasks, $storyBugs, $storyCases, $useDatatable ? 'datatable' : 'table');?>
</tr>
<?php if(!empty($story->children)):?>
<?php $i = 0;?>
<?php foreach($story->children as $key => $child):?>
<?php $class = $i == 0 ? ' table-child-top' : '';?>
<?php $class .= ($i + 1 == count($story->children)) ? ' table-child-bottom' : '';?>
<tr class='table-children<?php echo $class;?> parent-<?php echo $story->id;?>' data-id='<?php echo $child->id?>' data-status='<?php echo $child->status?>' data-estimate='<?php echo $child->estimate?>'>
<?php foreach($setting as $key => $value) $this->story->printCell($value, $child, $users, $branches, $storyStages, $modulePairs, $storyTasks, $storyBugs, $storyCases, $useDatatable ? 'datatable' : 'table');?>
</tr>
<?php $i ++;?>
<?php endforeach;?>
<?php endif;?>
<?php endforeach;?>
</tbody>
</table>
+1 -1
View File
@@ -398,7 +398,7 @@ class productplan extends control
}
else
{
$allStories = $this->story->getProductStories($this->view->product->id, $plan->branch ? "0,{$plan->branch}" : 0, $moduleID = '0', $status = 'draft,active,changed');
$allStories = $this->story->getProductStories($this->view->product->id, $plan->branch ? "0,{$plan->branch}" : 0, $moduleID = '0', $status = 'draft,active,changed', null, $hasParent = false);
}
$this->view->allStories = $allStories;
+7 -1
View File
@@ -41,6 +41,7 @@
<tbody class='text-center'>
<?php $unlinkedCount = 0;?>
<?php foreach($allStories as $story):?>
<?php if($story->parent < 0) continue;?>
<?php if(isset($planStories[$story->id])) continue;?>
<tr>
<td class='c-id text-left'>
@@ -49,7 +50,12 @@
<td><span class='label-pri <?php echo 'label-pri-' . $story->pri;?>' title='<?php echo zget($lang->story->priList, $story->pri, $story->pri)?>'><?php echo zget($lang->story->priList, $story->pri, $story->pri)?></span></td>
<td><?php echo $story->planTitle;?></td>
<td title='<?php echo $modules[$story->module]?>' class='text-left'><?php echo $modules[$story->module];?></td>
<td class='text-left nobr' title='<?php echo $story->title?>'><?php echo html::a($this->createLink('story', 'view', "storyID=$story->id", '', true), $story->title, '', "data-toggle='modal' data-type='iframe' data-width='90%'");?></td>
<td class='text-left nobr' title='<?php echo $story->title?>'>
<?php
if($story->parent > 0) echo "<span class='label'>{$lang->story->childrenAB}</span>";
echo html::a($this->createLink('story', 'view', "storyID=$story->id", '', true), $story->title, '', "data-toggle='modal' data-type='iframe' data-width='90%'");
?>
</td>
<td><?php echo zget($users, $story->openedBy);?></td>
<td><?php echo zget($users, $story->assignedTo);?></td>
<td><?php echo $story->estimate;?></td>
+6 -1
View File
@@ -145,7 +145,12 @@
<?php if($canOrder):?><td class='sort-handler'><i class='icon-move'></i></td><?php endif;?>
<td><span class='label-pri <?php echo 'label-pri-' . $story->pri;?>' title='<?php echo zget($lang->story->priList, $story->pri, $story->pri);?>'><?php echo zget($lang->story->priList, $story->pri, $story->pri);?></span></td>
<td class='text-left nobr'><?php echo zget($modulePairs, $story->module, '');?></td>
<td class='text-left nobr' title='<?php echo $story->title?>'><?php echo html::a($viewLink , $story->title);?></td>
<td class='text-left nobr' title='<?php echo $story->title?>'>
<?php
if($story->parent > 0) echo "<span class='label'>{$lang->story->childrenAB}</span>";
echo html::a($viewLink , $story->title);
?>
</td>
<td><?php echo zget($users, $story->openedBy);?></td>
<td><?php echo zget($users, $story->assignedTo);?></td>
<td><?php echo $story->estimate;?></td>
+5 -1
View File
@@ -2008,7 +2008,11 @@ class project extends control
}
else
{
$allStories = $this->story->getProductStories(array_keys($products), $branches, $moduleID = '0', $status = 'active');
$allStories = $this->story->getProductStories(array_keys($products), $branches, $moduleID = '0', $status = 'active', $pager = null, $hasParent = false);
}
foreach($allStories as $id => $story)
{
if(isset($prjStories[$story->id])) unset($allStories[$id]);
}
$prjStories = $this->story->getProjectStoryPairs($projectID);
+6 -1
View File
@@ -64,7 +64,12 @@
<td><span class='label-pri <?php echo 'label-pri-' . $story->pri;?>' title='<?php echo zget($lang->story->priList, $story->pri, $story->pri);?>'><?php echo zget($lang->story->priList, $story->pri, $story->pri);?></span></td>
<td class='text-left' title='<?php echo $products[$story->product]->name?>'><?php echo html::a($this->createLink('product', 'browse', "productID=$story->product&branch=$story->branch"), $products[$story->product]->name, '_blank');?></td>
<td class='text-left' title='<?php echo zget($modules, $story->module, '')?>'><?php echo zget($modules, $story->module, '')?></td>
<td class='text-left nobr' title="<?php echo $story->title?>"><?php echo html::a($storyLink, $story->title);?></td>
<td class='text-left nobr' title="<?php echo $story->title?>">
<?php
if($story->parent > 0) echo "<span class='label'>{$lang->story->childrenAB}</span>";
echo html::a($storyLink, $story->title);
?>
</td>
<td title='<?php echo $story->planTitle;?>'><?php echo $story->planTitle;?></td>
<td><?php echo zget($lang->story->stageList, $story->stage);?></td>
<?php if($productType != 'normal'):?>
+1
View File
@@ -158,6 +158,7 @@
<td class='c-pri'><span class='label-pri <?php echo 'label-pri-' . $story->pri?>' title='<?php echo zget($lang->story->priList, $story->pri, $story->pri);?>'><?php echo zget($lang->story->priList, $story->pri, $story->pri);?></span></td>
<td class='c-name' title="<?php echo $story->title?>">
<?php if(isset($branchGroups[$story->product][$story->branch])) echo "<span class='label label-outline label-badge'>" . $branchGroups[$story->product][$story->branch] . '</span>';?>
<?php if($story->parent > 0) echo "<span class='label'>{$lang->story->childrenAB}</span>";?>
<?php echo html::a($storyLink,$story->title, null, "style='color: $story->color'");?>
</td>
<td class='c-user' title='<?php echo zget($users, $story->openedBy);?>'><?php echo zget($users, $story->openedBy);?></td>
+6 -1
View File
@@ -50,7 +50,12 @@
<?php printf('%03d', $story->id);?>
</td>
<td><span class='label-pri <?php echo 'label-pri-' . $story->pri;?>' title='<?php echo zget($lang->story->priList, $story->pri)?>'><?php echo zget($lang->story->priList, $story->pri)?></span></td>
<td class='text-left nobr' title='<?php echo $story->title?>'><?php echo html::a($this->createLink('story', 'view', "storyID=$story->id", '', true), $story->title, '', "data-toggle='modal' data-type='iframe' data-width='90%'");?></td>
<td class='text-left nobr' title='<?php echo $story->title?>'>
<?php
if($story->parent > 0) echo "<span class='label'>{$lang->story->childrenAB}</span>";
echo html::a($this->createLink('story', 'view', "storyID=$story->id", '', true), $story->title, '', "data-toggle='modal' data-type='iframe' data-width='90%'");
?>
</td>
<td><?php echo zget($users, $story->openedBy);?></td>
<td><?php echo zget($users, $story->assignedTo);?></td>
<td><?php echo $story->estimate;?></td>
+6 -1
View File
@@ -105,7 +105,12 @@
<?php echo sprintf('%03d', $story->id);?>
</td>
<td><span class='label-pri <?php echo 'label-pri-' . $story->pri;?>' title='<?php echo zget($lang->story->priList, $story->pri, $story->pri);?>'><?php echo zget($lang->story->priList, $story->pri, $story->pri);?></span></td>
<td class='text-left nobr' title='<?php echo $story->title?>'><?php echo html::a($storyLink,$story->title, '', "class='preview'");?></td>
<td class='text-left nobr' title='<?php echo $story->title?>'>
<?php
if($story->parent > 0) echo "<span class='label'>{$lang->story->childrenAB}</span>";
echo html::a($storyLink,$story->title, '', "class='preview'");
?>
</td>
<td><?php echo zget($users, $story->openedBy);?></td>
<td><?php echo $story->estimate;?></td>
<td>
+2 -2
View File
@@ -44,7 +44,7 @@ $lang->repo->name = 'Name';
$lang->repo->path = 'Path';
$lang->repo->prefix = 'Prefix';
$lang->repo->config = 'Config';
$lang->repo->desc = 'Describe';
$lang->repo->desc = 'Description';
$lang->repo->account = 'Username';
$lang->repo->password = 'Password';
$lang->repo->encoding = 'Encoding';
@@ -166,7 +166,7 @@ $lang->repo->example->client = new stdclass();
$lang->repo->example->path = new stdclass();
$lang->repo->example->client->git = "e.g. /usr/bin/git";
$lang->repo->example->client->svn = "e.g. /usr/bin/svn";
$lang->repo->example->path->git = "e.g. /homt/user/myproject";
$lang->repo->example->path->git = "e.g. /home/user/myproject";
$lang->repo->example->path->svn = "e.g. http://example.googlecode.com/svn/trunk/myproject";
$lang->repo->example->config = "Config directory is required in https. Use '--config-dir' to generate config dir.";
$lang->repo->example->encoding = "input encoding of files";
+2 -2
View File
@@ -44,7 +44,7 @@ $lang->repo->name = 'Name';
$lang->repo->path = 'Path';
$lang->repo->prefix = 'Prefix';
$lang->repo->config = 'Config';
$lang->repo->desc = 'Describe';
$lang->repo->desc = 'Description';
$lang->repo->account = 'Username';
$lang->repo->password = 'Password';
$lang->repo->encoding = 'Encoding';
@@ -166,7 +166,7 @@ $lang->repo->example->client = new stdclass();
$lang->repo->example->path = new stdclass();
$lang->repo->example->client->git = "e.g. /usr/bin/git";
$lang->repo->example->client->svn = "e.g. /usr/bin/svn";
$lang->repo->example->path->git = "e.g. /homt/user/myproject";
$lang->repo->example->path->git = "e.g. /home/user/myproject";
$lang->repo->example->path->svn = "e.g. http://example.googlecode.com/svn/trunk/myproject";
$lang->repo->example->config = "Config directory is required in https. Use '--config-dir' to generate config dir.";
$lang->repo->example->encoding = "input encoding of files";
+2 -2
View File
@@ -44,7 +44,7 @@ $lang->repo->name = 'Name';
$lang->repo->path = 'Path';
$lang->repo->prefix = 'Prefix';
$lang->repo->config = 'Config';
$lang->repo->desc = 'Describe';
$lang->repo->desc = 'Description';
$lang->repo->account = 'Username';
$lang->repo->password = 'Password';
$lang->repo->encoding = 'Encoding';
@@ -166,7 +166,7 @@ $lang->repo->example->client = new stdclass();
$lang->repo->example->path = new stdclass();
$lang->repo->example->client->git = "e.g. /usr/bin/git";
$lang->repo->example->client->svn = "e.g. /usr/bin/svn";
$lang->repo->example->path->git = "e.g. /homt/user/myproject";
$lang->repo->example->path->git = "e.g. /home/user/myproject";
$lang->repo->example->path->svn = "e.g. http://example.googlecode.com/svn/trunk/myproject";
$lang->repo->example->config = "Config directory is required in https. Use '--config-dir' to generate config dir.";
$lang->repo->example->encoding = "input encoding of files";
+1 -1
View File
@@ -167,7 +167,7 @@ $lang->repo->example->client = new stdclass();
$lang->repo->example->path = new stdclass();
$lang->repo->example->client->git = "例如:/usr/bin/git";
$lang->repo->example->client->svn = "例如:/usr/bin/svn";
$lang->repo->example->path->git = "例如:/homt/user/myproject";
$lang->repo->example->path->git = "例如:/home/user/myproject";
$lang->repo->example->path->svn = "例如:http://example.googlecode.com/svn/trunk/myproject";
$lang->repo->example->config = "https需要填写配置目录的位置,通过config-dir选项生成配置目录";
$lang->repo->example->encoding = "填写版本库中文件的编码";
+1 -1
View File
@@ -167,7 +167,7 @@ $lang->repo->example->client = new stdclass();
$lang->repo->example->path = new stdclass();
$lang->repo->example->client->git = "例如:/usr/bin/git";
$lang->repo->example->client->svn = "例如:/usr/bin/svn";
$lang->repo->example->path->git = "例如:/homt/user/myproject";
$lang->repo->example->path->git = "例如:/home/user/myproject";
$lang->repo->example->path->svn = "例如:http://example.googlecode.com/svn/trunk/myproject";
$lang->repo->example->config = "https需要填寫配置目錄的位置,通過config-dir選項生成配置目錄";
$lang->repo->example->encoding = "填寫版本庫中檔案的編碼";
+1 -1
View File
@@ -194,5 +194,5 @@ $config->story->datatable->fieldList['caseCount']['name'] = $lang->story->ca
$config->story->datatable->fieldList['actions']['title'] = 'actions';
$config->story->datatable->fieldList['actions']['fixed'] = 'right';
$config->story->datatable->fieldList['actions']['width'] = '150';
$config->story->datatable->fieldList['actions']['width'] = '180';
$config->story->datatable->fieldList['actions']['required'] = 'yes';
+47 -3
View File
@@ -294,6 +294,13 @@ class story extends control
*/
public function batchCreate($productID = 0, $branch = 0, $moduleID = 0, $storyID = 0, $project = 0, $plan = 0, $type = 'story')
{
/* Check can subdivide or not. */
if($storyID)
{
$story = $this->story->getById($storyID);
if($story->status != 'active' or $story->stage != 'wait' or $story->parent > 0) die(js::alert($this->lang->story->errorNotSubdivide));
}
if(!empty($_POST))
{
$mails = $this->story->batchCreate($productID, $branch, $type);
@@ -307,7 +314,7 @@ class story extends control
if($storyID)
{
if(empty($mails)) die(js::closeModal('parent.parent', 'this'));
$actionID = $this->story->subdivide($storyID, $mails);
$this->story->subdivide($storyID, $stories);
if(dao::isError()) die(js::error(dao::getError()));
@@ -474,6 +481,7 @@ class story extends control
$this->view->title = $this->lang->story->edit . "STORY" . $this->lang->colon . $this->view->story->title;
$this->view->position[] = $this->lang->story->edit;
$this->view->story = $story;
$this->view->stories = $this->story->getParentStoryPairs($story->product, $story->parent);
$this->view->users = $this->user->getPairs('pofirst|nodeleted', "$story->assignedTo,$story->openedBy,$story->closedBy");
$this->view->product = $product;
$this->view->branches = $product->type == 'normal' ? array() : $this->loadModel('branch')->getPairs($story->product);
@@ -766,6 +774,9 @@ class story extends control
*/
public function delete($storyID, $confirm = 'no')
{
$story = $this->story->getById($storyID);
if($story->parent < 0) die(js::alert($this->lang->story->cannotDeleteParent));
if($confirm == 'no')
{
echo js::confirm($this->lang->story->confirmDelete, $this->createLink('story', 'delete', "story=$storyID&confirm=yes"), '');
@@ -774,6 +785,11 @@ class story extends control
else
{
$this->story->delete(TABLE_STORY, $storyID);
if($story->parent > 0)
{
$this->story->updateParentStatus($story->id);
$this->loadModel('action')->create('story', $task->parent, 'deleteChildrenStory', '', $storyID);
}
$this->executeHooks($storyID);
@@ -1316,7 +1332,7 @@ class story extends control
* @access public
* @return void
*/
public function ajaxGetProductStories($productID, $branch = 0, $moduleID = 0, $storyID = 0, $onlyOption = 'false', $status = '', $limit = 0, $type = 'full')
public function ajaxGetProductStories($productID, $branch = 0, $moduleID = 0, $storyID = 0, $onlyOption = 'false', $status = '', $limit = 0, $type = 'full', $hasParent = 1)
{
if($moduleID)
{
@@ -1332,7 +1348,7 @@ class story extends control
$storyStatus = array_keys($storyStatus);
}
$stories = $this->story->getProductStoryPairs($productID, $branch ? "0,$branch" : $branch, $moduleID, $storyStatus, 'id_desc', $limit, $type);
$stories = $this->story->getProductStoryPairs($productID, $branch ? "0,$branch" : $branch, $moduleID, $storyStatus, 'id_desc', $limit, $type, 'story', $hasParent);
$select = html::select('story', empty($stories) ? array('' => '') : $stories, $storyID, "class='form-control'");
/* If only need options, remove select wrap. */
@@ -1511,6 +1527,32 @@ class story extends control
while($row = $stmt->fetch()) $stories[$row->id] = $row;
}
if($stories)
{
$children = array();
foreach($stories as $story)
{
if($story->parent > 0 and isset($stories[$story->parent]))
{
$children[$story->parent][$story->id] = $story;
unset($stories[$story->id]);
}
}
if(!empty($children))
{
$position = 0;
foreach($stories as $story)
{
$position ++;
if(isset($children[$story->id]))
{
array_splice($stories, $position, 0, $children[$story->id]);
$position += count($children[$story->id]);
}
}
}
}
/* Get users, products and projects. */
$users = $this->loadModel('user')->getPairs('noletter');
$products = $this->loadModel('product')->getPairs('nocode');
@@ -1667,6 +1709,8 @@ class story extends control
}
$story->reviewedBy = rtrim($story->reviewedBy, ',');
/* Set child story title. */
if($story->parent > 0 && strpos($story->title, htmlentities('>')) !== 0) $story->title = '>' . $story->title;
}
if($projectID)
+5
View File
@@ -61,6 +61,7 @@ $lang->story->viewAll = '查看全部';
$lang->story->common = $lang->storyCommon;
$lang->story->id = '编号';
$lang->story->parent = '父需求';
$lang->story->product = "所属{$lang->productCommon}";
$lang->story->branch = "分支/平台";
$lang->story->module = '所属模块';
@@ -101,6 +102,8 @@ $lang->story->version = '版本号';
$lang->story->plan = '所属计划';
$lang->story->planAB = '计划';
$lang->story->comment = '备注';
$lang->story->children = "子{$lang->storyCommon}";
$lang->story->childrenAB = "子";
$lang->story->linkStories = "相关{$lang->storyCommon}";
$lang->story->childStories = "细分{$lang->storyCommon}";
$lang->story->duplicateStory = "重复{$lang->storyCommon}ID";
@@ -213,10 +216,12 @@ $lang->story->needNotReview = '不需要评审';
$lang->story->successSaved = "{$lang->storyCommon}成功添加,";
$lang->story->confirmDelete = "您确认删除该{$lang->storyCommon}吗?";
$lang->story->errorEmptyChildStory = "『细分{$lang->storyCommon}』不能为空。";
$lang->story->errorNotSubdivide = "状态不是激活,或者阶段不是未开始的{$lang->storyCommon},或者是子需求,则不能细分。";
$lang->story->mustChooseResult = '必须选择评审结果';
$lang->story->mustChoosePreVersion = '必须选择回溯的版本';
$lang->story->noStory = "暂时没有{$lang->storyCommon}。";
$lang->story->ignoreChangeStage = "{$lang->storyCommon} %s 为草稿状态,没有修改其阶段。";
$lang->story->cannotDeleteParent = "不能删除父{$lang->storyCommon}";
$lang->story->form = new stdclass();
$lang->story->form->area = "该{$lang->storyCommon}所属范围";
+265 -30
View File
@@ -33,6 +33,9 @@ class storyModel extends model
$story->spec = isset($spec->spec) ? $spec->spec : '';
$story->verify = isset($spec->verify) ? $spec->verify : '';
/* Check parent story. */
if($story->parent > 0) $story->parentName = $this->dao->findById($story->parent)->from(TABLE_STORY)->fetch('title');
$story = $this->loadModel('file')->replaceImgURL($story, 'spec,verify');
if($setImgSize) $story->spec = $this->file->setImgSize($story->spec);
if($setImgSize) $story->verify = $this->file->setImgSize($story->verify);
@@ -62,6 +65,10 @@ class storyModel extends model
if($story->childStories) $extraStories = array_merge($extraStories, explode(',', $story->childStories));
$extraStories = array_unique($extraStories);
if(!empty($extraStories)) $story->extraStories = $this->dao->select('id,title')->from(TABLE_STORY)->where('id')->in($extraStories)->fetchPairs();
$story->children = array();
if($story->parent == '-1') $story->children = $this->dao->select('*')->from(TABLE_STORY)->where('parent')->eq($storyID)->andWhere('deleted')->eq(0)->fetchAll('id');
return $story;
}
@@ -574,11 +581,179 @@ class storyModel extends model
$this->dao->replace(TABLE_PROJECTPRODUCT)->data($data)->exec();
}
}
$this->loadModel('action');
$changed = $story->parent != $oldStory->parent;
if($oldStory->parent > 0)
{
$oldParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($oldStory->parent)->fetch();
$this->updateParentStatus($storyID, $oldStory->parent, !$changed);
if($changed)
{
$oldChildren = $this->dao->select('id')->from(TABLE_STORY)->where('parent')->eq($oldStory->parent)->andWhere('deleted')->eq(0)->fetchPairs('id', 'id');
if(empty($oldChildren)) $this->dao->update(TABLE_STORY)->set('parent')->eq(0)->where('id')->eq($oldStory->parent)->exec();
$this->dao->update(TABLE_STORY)->set('childStories')->eq(join(',', $oldChildren))->set('lastEditedBy')->eq($this->app->user->account)->set('lastEditedDate')->eq(helper::now())->where('id')->eq($oldStory->parent)->exec();
$this->action->create('story', $storyID, 'unlinkParentStory', '', $oldStory->parent, '', false);
$actionID = $this->action->create('story', $oldStory->parent, 'unLinkChildrenStory', '', $storyID, '', false);
$newParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($oldStory->parent)->fetch();
$changes = common::createChanges($oldParentStory, $newParentStory);
if(!empty($changes)) $this->action->logHistory($actionID, $changes);
}
}
if($story->parent > 0)
{
$parentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($story->parent)->fetch();
$this->dao->update(TABLE_STORY)->set('parent')->eq(-1)->where('id')->eq($story->parent)->exec();
$this->updateParentStatus($storyID, $story->parent, !$changed);
if($changed)
{
$children = $this->dao->select('id')->from(TABLE_STORY)->where('parent')->eq($story->parent)->andWhere('deleted')->eq(0)->fetchPairs('id', 'id');
$this->dao->update(TABLE_STORY)
->set('parent')->eq('-1')
->set('childStories')->eq(join(',', $children))
->set('lastEditedBy')->eq($this->app->user->account)
->set('lastEditedDate')->eq(helper::now())
->where('id')->eq($story->parent)
->exec();
$this->action->create('story', $storyID, 'linkParentStory', '', $story->parent, '', false);
$actionID = $this->action->create('story', $story->parent, 'linkChildStory', '', $storyID, '', false);
$newParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($story->parent)->fetch();
$changes = common::createChanges($parentStory, $newParentStory);
if(!empty($changes)) $this->action->logHistory($actionID, $changes);
}
}
if(isset($story->closedReason) and $story->closedReason == 'done') $this->loadModel('score')->create('story', 'close');
unset($oldStory->parent);
unset($story->parent);
return common::createChanges($oldStory, $story);
}
}
/**
* Update parent status.
*
* @param int $storyID
* @param int $parentID
* @param bool $createAction
* @access public
* @return mixed
*/
public function updateParentStatus($storyID, $parentID = 0, $createAction = true)
{
$childStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($storyID)->fetch();
if(empty($parentID)) $parentID = $childStory->parent;
if($parentID <= 0) return true;
$oldParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($parentID)->andWhere('deleted')->eq(0)->fetch();
if(empty($oldParentStory)) return $this->dao->update(TABLE_STORY)->set('parent')->eq('0')->where('id')->eq($storyID)->exec();
if($oldParentStory->parent != '-1') $this->dao->update(TABLE_STORY)->set('parent')->eq('-1')->where('id')->eq($parentID)->exec();
$this->computeEstimate($parentID);
$childrenStatus = $this->dao->select('id,status')->from(TABLE_STORY)->where('parent')->eq($parentID)->andWhere('deleted')->eq(0)->fetchPairs('status', 'status');
if(empty($childrenStatus)) return $this->dao->update(TABLE_STORY)->set('parent')->eq('0')->where('id')->eq($parentID)->exec();
$status = $oldParentStory->status;
if(count($childrenStatus) == 1 and $oldParentStory->status != 'changed')
{
$status = current($childrenStatus);
if($status == 'draft' or $status == 'changed') $status = 'active';
}
elseif(count($childrenStatus) != 1 and $oldParentStory->status == 'closed')
{
$status = 'active';
}
if($status and $oldParentStory->status != $status)
{
$now = helper::now();
$story = new stdclass();
$story->status = $status;
$story->stage = 'wait';
if($status == 'active')
{
$story->assignedTo = $oldParentStory->openedBy;
$story->assignedDate = $now;
$story->closedBy = '';
$story->closedReason = '';
$story->closedDate = '0000-00-00';
$story->reviewedBy = '';
$story->reviewedDate = '0000-00-00';
}
if($status == 'closed')
{
$story->assignedTo = 'closed';
$story->assignedDate = $now;
$story->closedBy = $this->app->user->account;
$story->closedDate = $now;
$story->closedReason = 'done';
$story->closedReason = 'done';
}
$story->lastEditedBy = $this->app->user->account;
$story->lastEditedDate = $now;
$story->parent = '-1';
$this->dao->update(TABLE_STORY)->data($story)->where('id')->eq($parentID)->exec();
if(!dao::isError())
{
if(!$createAction) return $story;
$newParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($parentID)->fetch();
$changes = common::createChanges($oldParentStory, $newParentStory);
$action = '';
if($status == 'active') $action = 'Activated';
if($status == 'closed') $action = 'Closed';
if($action)
{
$actionID = $this->loadModel('action')->create('story', $parentID, $action, '', '', '', false);
$this->action->logHistory($actionID, $changes);
}
}
}
else
{
if(!dao::isError())
{
$newParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($parentID)->fetch();
$changes = common::createChanges($oldParentStory, $newParentStory);
if($changes)
{
$actionID = $this->loadModel('action')->create('story', $parentID, 'Edited', '', '', '', false);
$this->action->logHistory($actionID, $changes);
}
}
}
}
/**
* Compute parent story estimate.
*
* @param int $storyID
* @access public
* @return bool
*/
public function computeEstimate($storyID)
{
if(!$storyID) return true;
$stories = $this->dao->select('`id`,`estimate`,status')->from(TABLE_STORY)->where('parent')->eq($storyID)->andWhere('deleted')->eq(0)->fetchAll('id');
if(empty($stories)) return true;
$estimate = 0;
foreach($stories as $story) $estimate += $story->estimate;
$this->dao->update(TABLE_STORY)->set('estimate')->eq($estimate)->autoCheck()->where('id')->eq($storyID)->exec();
return !dao::isError();
}
/**
* Batch update stories.
*
@@ -810,40 +985,36 @@ class storyModel extends model
* @param int $storyID
* @param array $stories
* @access public
* @return int
* @return void
*/
public function subdivide($storyID, $stories)
{
$now = helper::now();
$oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch();
/* Set parent to child story. */
$this->dao->update(TABLE_STORY)->set('parent')->eq($storyID)->where('id')->in($stories)->exec();
$this->computeEstimate($storyID);
/* Set childStories. */
$childStories = '';
foreach($stories as $story) $childStories .= $story->storyID . ',';
$childStories = trim($childStories, ',');
$childStories = join(',', $stories);
$newStory = new stdClass();
$newStory->parent = '-1';
$newStory->plan = 0;
$newStory->lastEditedBy = $this->app->user->account;
$newStory->lastEditedDate = $now;
$newStory->closedDate = $now;
$newStory->closedBy = $this->app->user->account;
$newStory->assignedTo = 'closed';
$newStory->assignedDate = $now;
$newStory->status = 'closed';
$newStory->closedReason = 'subdivided';
$newStory->childStories = $childStories;
$newStory->childStories = trim($oldStory->childStories . ',' . $childStories, ',');
/* Subdivide story and close it. */
$this->dao->update(TABLE_STORY)->data($newStory)
->autoCheck()
->batchCheck($this->config->story->close->requiredFields, 'notempty')
->where('id')->eq($storyID)->exec();
$changes = common::createChanges($oldStory, $newStory);
$actionID = $this->loadModel('action')->create('story', $storyID, 'Closed', '', 'Subdivided');
$this->action->logHistory($actionID, $changes);
/* Subdivide story. */
$this->dao->update(TABLE_STORY)->data($newStory)->autoCheck()->where('id')->eq($storyID)->exec();
return $actionID;
$changes = common::createChanges($oldStory, $newStory);
if($changes)
{
$actionID = $this->loadModel('action')->create('story', $storyID, 'createChildrenStory', '', $childStories);
$this->action->logHistory($actionID, $changes);
}
}
/**
@@ -876,6 +1047,10 @@ class storyModel extends model
->batchCheck($this->config->story->close->requiredFields, 'notempty')
->checkIF($story->closedReason == 'duplicate', 'duplicateStory', 'notempty')
->where('id')->eq($storyID)->exec();
/* Update parent story status. */
if($oldStory->parent > 0) $this->updateParentStatus($storyID, $oldStory->parent);
if(!dao::isError()) $this->loadModel('score')->create('story', 'close', $storyID);
return common::createChanges($oldStory, $story);
}
@@ -934,6 +1109,9 @@ class storyModel extends model
if(!dao::isError())
{
/* Update parent story status. */
if($oldStory->parent > 0) $this->updateParentStatus($storyID, $oldStory->parent);
$allChanges[$storyID] = common::createChanges($oldStory, $story);
}
else
@@ -1164,6 +1342,9 @@ class storyModel extends model
$this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq($storyID)->exec();
$this->setStage($storyID);
/* Update parent story status. */
if($oldStory->parent > 0) $this->updateParentStatus($storyID, $oldStory->parent);
return common::createChanges($oldStory, $story);
}
@@ -1392,11 +1573,12 @@ class storyModel extends model
* @param string $type requirement|story
* @param string $orderBy
* @param object $pager
* @param bool $hasParent
*
* @access public
* @return array
*/
public function getProductStories($productID = 0, $branch = 0, $moduleIdList = 0, $status = 'all', $type = 'story', $orderBy = 'id_desc', $pager = null)
public function getProductStories($productID = 0, $branch = 0, $moduleIdList = 0, $status = 'all', $type = 'story', $orderBy = 'id_desc', $pager = null, $hasParent = true)
{
if(defined('TUTORIAL')) return $this->loadModel('tutorial')->getStories();
@@ -1408,12 +1590,13 @@ class storyModel extends model
}
$stories = $this->dao->select('*')->from(TABLE_STORY)
->where('product')->in($productID)
->beginIF(!$hasParent)->andWhere("parent")->ge(0)->fi()
->beginIF($branch)->andWhere("branch")->in($branch)->fi()
->beginIF(!empty($moduleIdList))->andWhere('module')->in($moduleIdList)->fi()
->beginIF($status and $status != 'all')->andWhere('status')->in($status)->fi()
->andWhere('deleted')->eq(0)
->andWhere('type')->eq($type)
->orderBy($orderBy)->page($pager)->fetchAll();
->orderBy($orderBy)->page($pager)->fetchAll('id');
return $this->mergePlanTitle($productID, $stories, $branch);
}
@@ -1427,10 +1610,11 @@ class storyModel extends model
* @param int $limit
* @param string $type
* @param string $storyType requirement|story
* @param bool $hasParent
* @access public
* @return array
*/
public function getProductStoryPairs($productID = 0, $branch = 0, $moduleIdList = 0, $status = 'all', $order = 'id_desc', $limit = 0, $type = 'full', $storyType = 'story')
public function getProductStoryPairs($productID = 0, $branch = 0, $moduleIdList = 0, $status = 'all', $order = 'id_desc', $limit = 0, $type = 'full', $storyType = 'story', $hasParent = true)
{
if($branch) $branch = "0,$branch";//Fix bug 1059.
$stories = $this->dao->select('t1.id, t1.title, t1.module, t1.pri, t1.estimate, t2.name AS product')
@@ -1439,6 +1623,7 @@ class storyModel extends model
->beginIF($productID)->andWhere('t1.product')->in($productID)->fi()
->beginIF($moduleIdList)->andWhere('t1.module')->in($moduleIdList)->fi()
->beginIF($branch)->andWhere('t1.branch')->in($branch)->fi()
->beginIF(!$hasParent)->andWhere('t1.parent')->ge(0)->fi()
->beginIF($status and $status != 'all')->andWhere('t1.status')->in($status)->fi()
->andWhere('t1.deleted')->eq(0)
->andWhere('t1.type')->eq($storyType)
@@ -1571,7 +1756,7 @@ class storyModel extends model
->beginIF($operator == 'include')->andWhere($fieldName)->like("%$fieldValue%")->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll();
->fetchAll('id');
return $this->mergePlanTitle($productID, $stories, $branch);
}
@@ -1877,6 +2062,18 @@ class storyModel extends model
->fetchAll();
}
public function getParentStoryPairs($productID, $append = '')
{
$stories = $this->dao->select('id, title')->from(TABLE_STORY)
->where('deleted')->eq(0)
->andWhere('parent')->le(0)
->andWhere('status')->notin('closed,draft')
->andWhere('product')->eq($productID)
->beginIF($append)->orWhere('id')->in($append)->fi()
->fetchPairs();
return array(0 => '') + $stories ;
}
/**
* Get stories of a user.
*
@@ -1903,7 +2100,7 @@ class storyModel extends model
->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll();
->fetchAll('id');
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story', false);
$productIdList = array();
@@ -1984,7 +2181,7 @@ class storyModel extends model
*/
public function getZeroCase($productID, $orderBy = 'id_desc')
{
$allStories = $this->getProductStories($productID, 0, 0, 'all', 'story', $orderBy);
$allStories = $this->getProductStories($productID, 0, 0, 'all', 'story', $orderBy, null, $hasParent = false);
$casedStories = $this->dao->select('DISTINCT story')->from(TABLE_CASE)->where('product')->eq($productID)->andWhere('story')->ne(0)->andWhere('deleted')->eq(0)->fetchAll('story');
foreach($allStories as $key => $story)
@@ -2413,11 +2610,15 @@ class storyModel extends model
{
$action = strtolower($action);
if($story->parent < 0 and $action != 'edit' and $action != 'batchcreate') return false;
if($action == 'change') return $story->status != 'closed';
if($action == 'review') return $story->status == 'draft' or $story->status == 'changed';
if($action == 'close') return $story->status != 'closed';
if($action == 'activate') return $story->status == 'closed';
if($action == 'assignto') return $story->status != 'closed';
if($action == 'batchcreate' and $story->parent > 0) return false;
if($action == 'batchcreate' and ($story->status != 'active' or $story->stage != 'wait')) return false;
return true;
}
@@ -2446,8 +2647,31 @@ class storyModel extends model
->andWhere('deleted')->eq(0)
->fetchPairs('id', 'title');
$parents = array();
$tmpStories = array();
foreach($stories as $story)
{
$tmpStories[$story->id] = $story;
if($story->parent > 0) $parents[$story->parent] = $story->parent;
}
$parents = $this->dao->select('*')->from(TABLE_STORY)->where('id')->in($parents)->fetchAll('id');
foreach($stories as $storyID => $story)
{
if($story->parent > 0)
{
if(isset($stories[$story->parent]))
{
$stories[$story->parent]->children[$story->id] = $story;
unset($stories[$storyID]);
}
else
{
$parent = $parents[$story->parent];
$story->parentName = $story->title;
}
}
$story->planTitle = '';
$storyPlans = explode(',', trim($story->plan, ','));
foreach($storyPlans as $planID) $story->planTitle .= zget($plans, $planID, '') . ' ';
@@ -2484,14 +2708,21 @@ class storyModel extends model
$class = "c-{$id}";
$title = '';
if ($id == 'assignedTo')
if($id == 'assignedTo')
{
$title = zget($users, $story->assignedTo, $story->assignedTo);
if($story->assignedTo == $account) $class .= ' red';
}
else if($id == 'openedBy') $title = zget($users, $story->openedBy, $story->openedBy);
else if($id == 'title') $title = $story->title;
else if($id == 'plan')
elseif($id == 'openedBy')
{
$title = zget($users, $story->openedBy, $story->openedBy);
}
elseif($id == 'title')
{
$title = $story->title;
if(!empty($story->children)) $class .= ' has-child';
}
elseif($id == 'plan')
{
$title = $story->planTitle;
$class .= ' text-ellipsis';
@@ -2524,9 +2755,12 @@ class storyModel extends model
echo "</span>";
break;
case 'title':
if($story->parent > 0 and isset($story->parentName)) $story->title = "{$story->parentName} / {$story->title}";
if($story->branch and isset($branches[$story->branch])) echo "<span class='label label-outline label-badge'>{$branches[$story->branch]}</span> ";
if($story->module and isset($modulePairs[$story->module])) echo "<span class='label label-gray label-badge'>{$modulePairs[$story->module]}</span> ";
if($story->parent > 0) echo '<span class="label label-badge label-light" title="' . $this->lang->story->children . '">' . $this->lang->story->childrenAB . '</span> ';
echo $canView ? html::a($storyLink, $story->title, '', "style='color: $story->color'") : "<span style='color: $story->color'>{$story->title}</span>";
if(!empty($story->children)) echo '<a class="story-toggle" data-id="' . $story->id . '"><i class="icon icon-angle-double-right"></i></a>';
break;
case 'plan':
echo $story->planTitle;
@@ -2634,6 +2868,7 @@ class storyModel extends model
common::printIcon('story', 'close', $vars, $story, 'list', '', '', 'iframe', true);
common::printIcon('story', 'edit', $vars, $story, 'list');
if($this->config->global->flow != 'onlyStory') common::printIcon('story', 'createCase', "productID=$story->product&branch=$story->branch&module=0&from=&param=0&$vars", $story, 'list', 'sitemap');
common::printIcon('story', 'batchCreate', "productID=$story->product&branch=$story->branch&module=0&storyID=$story->id", $story, 'list', 'treemap-alt', '', '', '', '', $this->lang->story->subdivide);
break;
}
echo '</td>';
+1 -1
View File
@@ -13,7 +13,7 @@
<?php include './header.html.php';?>
<div id="mainContent" class="main-content">
<div class="main-header">
<h2><?php echo $lang->story->batchCreate;?></h2>
<h2><?php echo $storyID ? $this->lang->story->subdivide : $this->lang->story->batchCreate;?></h2>
<div class="pull-right btn-toolbar">
<?php if(common::hasPriv('file', 'uploadImages')) echo html::a($this->createLink('file', 'uploadImages', 'module=story&params=' . helper::safe64Encode("productID=$productID&branch=$branch&moduleID=$moduleID")), $lang->uploadImages, '', "data-toggle='modal' data-type='iframe' class='btn btn-primary' data-width='70%'")?>
<button type='button' data-toggle='modal' data-target="#importLinesModal" class="btn btn-primary"><?php echo $lang->pasteText;?></button>
+10 -2
View File
@@ -66,6 +66,7 @@
<div class='detail'>
<div class='detail-title'><?php echo $lang->story->legendBasicInfo;?></div>
<table class='table table-form'>
<?php if($story->parent <= 0):?>
<tr>
<th class='thWidth'><?php echo $lang->story->product;?></th>
<td>
@@ -76,8 +77,9 @@
</div>
</td>
</tr>
<?php endif;?>
<tr>
<th><?php echo $lang->story->module;?></th>
<th class='thWidth'><?php echo $lang->story->module;?></th>
<td>
<div class='input-group' id='moduleIdBox'>
<?php
@@ -94,6 +96,12 @@
</div>
</td>
</tr>
<?php if($story->parent >= 0):?>
<tr>
<th><?php echo $lang->story->parent;?></th>
<td><?php echo html::select('parent', $stories, $story->parent, "class='form-control chosen'");?></td>
</tr>
<?php endif;?>
<tr>
<th><?php echo $lang->story->plan;?></th>
<td>
@@ -153,7 +161,7 @@
</tr>
<tr>
<th><?php echo $lang->story->estimate;?></th>
<td><?php echo html::input('estimate', $story->estimate, "class='form-control'");?></td>
<td><?php echo $story->parent >= 0 ? html::input('estimate', $story->estimate, "class='form-control'") : $story->estimate;?></td>
</tr>
<tr>
<th><?php echo $lang->story->keywords;?></th>
+56 -4
View File
@@ -22,7 +22,10 @@
<?php endif;?>
<div class="page-title">
<span class="label label-id"><?php echo $story->id?></span>
<span class="text" title='<?php echo $story->title;?>' style='color: <?php echo $story->color;?>'><?php echo $story->title;?></span>
<span class="text" title='<?php echo $story->title;?>' style='color: <?php echo $story->color;?>'>
<?php if($story->parent > 0) echo '<span class="label label-badge label-primary no-margin">' . $this->lang->story->childrenAB . '</span>';?>
<?php if($story->parent > 0) echo isset($story->parentName) ? html::a(inlink('view', "storyID={$story->parent}"), $story->parentName) . ' / ' : '';?><?php echo $story->title;?>
</span>
<?php if($story->version > 1):?>
<small class='dropdown'>
<a href='#' data-toggle='dropdown' class='text-muted'><?php echo '#' . $version;?> <span class='caret'></span></a>
@@ -61,6 +64,55 @@
</div>
<?php echo $this->fetch('file', 'printFiles', array('files' => $story->files, 'fieldset' => 'true'));?>
<?php $actionFormLink = $this->createLink('action', 'comment', "objectType=story&objectID=$story->id");?>
<?php if(!empty($story->children)):?>
<div class='detail'>
<div class='detail-title'><?php echo $this->lang->story->children;?></div>
<div class='detail-content article-content'>
<table class='table table-hover table-fixed'>
<thead>
<tr class='text-center'>
<th class='w-50px'> <?php echo $lang->story->id;?></th>
<th class='w-40px'> <?php echo $lang->priAB;?></th>
<th> <?php echo $lang->story->title;?></th>
<th class='w-100px'><?php echo $lang->story->assignedTo;?></th>
<th class='w-80px'> <?php echo $lang->story->estimate;?></th>
<th class='w-80px'> <?php echo $lang->story->status;?></th>
<th class='w-200px'><?php echo $lang->actions;?></th>
</tr>
</thead>
<tbody>
<?php foreach($story->children as $child):?>
<tr class='text-center'>
<td><?php echo $child->id;?></td>
<td>
<?php
echo "<span class='pri-" . $child->pri . "'>";
echo $child->pri == '0' ? '' : zget($this->lang->story->priList, $child->pri, $child->pri);
echo "</span>";
?>
</td>
<td class='text-left' title='<?php echo $child->title;?>'><a class="iframe" data-width="90%" href="<?php echo $this->createLink('story', 'view', "storyID=$child->id", '', true); ?>"><?php echo $child->title;?></a></td>
<td><?php echo zget($users, $child->assignedTo);?></td>
<td><?php echo $child->estimate;?></td>
<td><?php echo $this->processStatus('story', $child);?></td>
<td class='c-actions'>
<?php
common::printIcon('story', 'change', "storyID=$child->id", $child, 'list');
common::printIcon('story', 'review', "storyID=$child->id", $child, 'list', '', '', 'iframe showinonlybody', true);
common::printIcon('story', 'assignTo', "storyID=$child->id", $child, 'list', '', '', 'iframe showinonlybody', true);
common::printIcon('story', 'close', "storyID=$child->id", $child, 'list', '', '', 'iframe showinonlybody', true);
common::printIcon('story', 'activate', "storyID=$child->id", $child, 'list', '', '', 'iframe showinonlybody', true);
common::printIcon('story', 'edit', "storyID=$child->id", $child, 'list');
common::printIcon('story', 'createCase', "productID=$child->product&branch=$child->branch&module=0&from=&param=0&story={$child->id}", $child, 'list', 'sitemap');
?>
</td>
</tr>
<?php endforeach;?>
</tbody>
</table>
</div>
</div>
<?php endif;?>
</div>
<?php $this->printExtendFields($story, 'div', "position=left&inForm=0&inCell=1");?>
<div class="cell"><?php include '../../common/view/action.html.php';?></div>
@@ -72,19 +124,19 @@
<?php
common::printIcon('story', 'change', "storyID=$story->id", $story, 'button', '', '', 'showinonlybody');
common::printIcon('story', 'review', "storyID=$story->id", $story, 'button', '', '', 'showinonlybody');
if($story->status != 'closed' and !isonlybody())
if($story->status == 'active' and $story->stage == 'wait' and $story->parent <= 0 and !isonlybody())
{
$divideLang = ($story->type == 'story' || !$story->type) ? $lang->story->subdivide : $lang->story->splitRequirent;
$misc = "class='btn divideStory' data-toggle='modal' data-type='iframe' data-width='95%'";
$link = $this->createLink('story', 'batchCreate', "productID=$story->product&branch=$story->branch&moduleID=$story->module&storyID=$story->id", '', true);
if(common::hasPriv('story', 'batchCreate')) echo html::a($link, "<i class='icon icon-sitemap'></i> " . $divideLang, '', $misc);
if(common::hasPriv('story', 'batchCreate')) echo html::a($link, "<i class='icon icon-treemap-alt'></i> " . $divideLang, '', $misc);
}
common::printIcon('story', 'assignTo', "storyID=$story->id", $story, 'button', '', '', 'iframe showinonlybody', true);
common::printIcon('story', 'close', "storyID=$story->id", $story, 'button', '', '', 'iframe showinonlybody', true);
common::printIcon('story', 'activate', "storyID=$story->id", $story, 'button', '', '', 'iframe showinonlybody', true);
if($config->global->flow != 'onlyStory' and !isonlybody() and (common::hasPriv('testcase', 'create') or common::hasPriv('testcase', 'batchCreate')))
if($config->global->flow != 'onlyStory' and !isonlybody() and $story->parent >= 0 and (common::hasPriv('testcase', 'create') or common::hasPriv('testcase', 'batchCreate')))
{
$this->app->loadLang('testcase');
echo "<div class='btn-group dropup'>";
+1 -1
View File
@@ -1393,7 +1393,7 @@ class task extends control
$children = array();
foreach($tasks as $task)
{
if(!empty($task->parent) and isset($tasks[$task->parent]))
if($task->parent > 0 and isset($tasks[$task->parent]))
{
$children[$task->parent][$task->id] = $task;
unset($tasks[$task->id]);
+1 -1
View File
@@ -328,7 +328,7 @@ class testcase extends control
$modules = $this->loadModel('tree')->getStoryModule($currentModuleID);
$modules = $this->tree->getAllChildID($modules);
}
$stories = $this->story->getProductStoryPairs($productID, $branch, $modules, array_keys($storyStatus), 'id_desc', 50, 'null');
$stories = $this->story->getProductStoryPairs($productID, $branch, $modules, array_keys($storyStatus), 'id_desc', 50, 'null', 'story', false);
if($storyID and !isset($stories[$storyID])) $stories = $this->story->formatStories(array($storyID => $story)) + $stories;//Fix bug #2406.
/* Set custom. */
+1 -1
View File
@@ -23,7 +23,7 @@ $(document).ready(function()
}
}
}
var link = createLink('story', 'ajaxGetProductStories', 'productID=' + productID + '&branch=' + branch + '&moduleID=' + moduleID + '&storyID='+ ($(select).val() || '0') + '&onlyOption=true&status=noclosed&limit=0&type=null');
var link = createLink('story', 'ajaxGetProductStories', 'productID=' + productID + '&branch=' + branch + '&moduleID=' + moduleID + '&storyID='+ ($(select).val() || '0') + '&onlyOption=true&status=noclosed&limit=0&type=null&hasParent=0');
var $story = $('#story' + index);
if($story.data('loadLink') !== link)
{