* Finish task #38180.

This commit is contained in:
holan20180123
2021-05-14 14:32:26 +08:00
parent 948525dcd3
commit daf14af385
19 changed files with 285 additions and 1 deletions
+1
View File
@@ -137,6 +137,7 @@ define('TABLE_STAKEHOLDER', '`' . $config->db->prefix . 'stakeholder`');
define('TABLE_STORY', '`' . $config->db->prefix . 'story`');
define('TABLE_STORYSPEC', '`' . $config->db->prefix . 'storyspec`');
define('TABLE_STORYSTAGE', '`' . $config->db->prefix . 'storystage`');
define('TABLE_STORYESTIMATE', '`' . $config->db->prefix . 'storyestimate`');
define('TABLE_PRODUCTPLAN', '`' . $config->db->prefix . 'productplan`');
define('TABLE_PLANSTORY', '`' . $config->db->prefix . 'planstory`');
define('TABLE_RELEASE', '`' . $config->db->prefix . 'release`');
+10
View File
@@ -8,6 +8,16 @@ CREATE TABLE IF NOT EXISTS `zt_storyreview` (
UNIQUE KEY `story` (`story`,`version`,`reviewer`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_storyestimate`;
CREATE TABLE IF NOT EXISTS `zt_storyestimate` (
`story` mediumint(9) NOT NULL,
`round` smallint(6) NOT NULL,
`estimate` text NOT NULL,
`average` float(10,2) NOT NULL,
`openedBy` varchar(30) NOT NULL,
`openedDate` datetime NOT NULL,
UNIQUE KEY `story` (`story`,`round`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
ALTER TABLE `zt_project` CHANGE `lifetime` `lifetime` char(30) NOT NULL DEFAULT '';
ALTER TABLE `zt_story` ADD `category` varchar(30) NOT NULL DEFAULT 'feature' AFTER `type`;
+10
View File
@@ -872,6 +872,16 @@ CREATE TABLE IF NOT EXISTS `zt_storyreview` (
`reivewDate` datetime NOT NULL,
UNIQUE KEY `story` (`story`,`version`,`reviewer`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_storyestimate`;
CREATE TABLE IF NOT EXISTS `zt_storyestimate` (
`story` mediumint(9) NOT NULL,
`round` smallint(6) NOT NULL,
`estimate` text NOT NULL,
`average` float(10,2) NOT NULL,
`openedBy` varchar(30) NOT NULL,
`openedDate` datetime NOT NULL,
UNIQUE KEY `story` (`story`,`round`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_storystage`;
CREATE TABLE IF NOT EXISTS `zt_storystage` (
`story` mediumint(8) unsigned NOT NULL,
+30
View File
@@ -2694,6 +2694,36 @@ class execution extends control
}
}
/**
* Story estimate.
*
* @param int $executionID
* @param int $storyID
* @param int $round
* @access public
* @return void
*/
public function storyEstimate($executionID, $storyID, $round = 0)
{
$this->loadModel('story');
if($_POST)
{
$this->story->saveEstimateInfo($storyID);
$this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('execution', 'storyEstimate', "executionID=$executionID&storyID=$storyID")));
}
$estimateInfo = $this->story->getEstimateInfo($storyID, $round);
$this->view->estimateInfo = $estimateInfo;
$this->view->round = !empty($estimateInfo->round) ? $estimateInfo->round : 0;
$this->view->rounds = $this->story->getEstimateRounds($storyID);
$this->view->users = $this->loadModel('user')->getPairs('noletter');
$this->view->team = $this->execution->getTeamMembers($executionID);
$this->view->executionID = $executionID;
$this->view->storyID = $storyID;
$this->display();
}
/**
* All execution.
*
+36
View File
@@ -0,0 +1,36 @@
$('.new-estimate input').keyup(function()
{
computeAverage();
})
function computeAverage()
{
var summary = 0;
var count = 0;
var average = 0;
$('.new-estimate').each(function()
{
var value = $(this).find('input').val();
if(!value) return false;
value = parseFloat(value);
if(isNaN(value)) value = 0;
summary += value;
count += 1;
})
if(count) average = summary / count;
$('#average').val(average.toFixed(2));
$('#showAverage').html(average.toFixed(2));
}
function showNewEstimate()
{
$('.th-new-estimate').removeClass('hide');
$('.new-estimate').removeClass('hide');
$('.form-actions').removeClass('hide');
}
function selectRound(round)
{
location.href = createLink('execution', 'storyEstimate', 'executionID=' + executionID + '&storyID=' + storyID + '&round=' + round);
}
+5
View File
@@ -80,6 +80,11 @@ $lang->execution->product = $lang->execution->products;
$lang->execution->readjustTime = 'Start und Ende anpassen';
$lang->execution->readjustTask = 'Fälligkeit der Aufgabe anpassen';
$lang->execution->effort = 'Aufwand';
$lang->execution->storyEstimate = 'Story Estimate';
$lang->execution->newEstimate = 'New Estimate';
$lang->execution->reestimate = 'Reestimate';
$lang->execution->selectRound = 'Select Round';
$lang->execution->average = 'Average';
$lang->execution->relatedMember = 'Teammitglieder';
$lang->execution->watermark = 'Exported by ZenTao';
$lang->execution->burnXUnit = '(Date)';
+5
View File
@@ -80,6 +80,11 @@ $lang->execution->product = $lang->execution->products;
$lang->execution->readjustTime = "Adjust {$lang->executionCommon} Begin and End";
$lang->execution->readjustTask = 'Adjust Task Begin and End';
$lang->execution->effort = 'Effort';
$lang->execution->storyEstimate = 'Story Estimate';
$lang->execution->newEstimate = 'New Estimate';
$lang->execution->reestimate = 'Reestimate';
$lang->execution->selectRound = 'Select Round';
$lang->execution->average = 'Average';
$lang->execution->relatedMember = 'Team';
$lang->execution->watermark = 'Exported by ZenTao';
$lang->execution->burnXUnit = '(Date)';
+5
View File
@@ -80,6 +80,11 @@ $lang->execution->product = $lang->execution->products;
$lang->execution->readjustTime = "Ajuster Début et Fin du {$lang->executionCommon}";
$lang->execution->readjustTask = 'Ajuster Début et Fin de la Tâche';
$lang->execution->effort = 'Effort';
$lang->execution->storyEstimate = 'Story Estimate';
$lang->execution->newEstimate = 'New Estimate';
$lang->execution->reestimate = 'Reestimate';
$lang->execution->selectRound = 'Select Round';
$lang->execution->average = 'Average';
$lang->execution->relatedMember = 'Equipe';
$lang->execution->watermark = 'Exporté par ZenTao';
$lang->execution->burnXUnit = '(Date)';
+5
View File
@@ -80,6 +80,11 @@ $lang->execution->product = $lang->execution->products;
$lang->execution->readjustTime = "Điều chỉnh {$lang->executionCommon} Thời gian";
$lang->execution->readjustTask = 'Điều chỉnh nhiệm vụ Thời gian';
$lang->execution->effort = 'Chấm công';
$lang->execution->storyEstimate = 'Story Estimate';
$lang->execution->newEstimate = 'New Estimate';
$lang->execution->reestimate = 'Reestimate';
$lang->execution->selectRound = 'Select Round';
$lang->execution->average = 'Average';
$lang->execution->relatedMember = 'Đội nhóm';
$lang->execution->watermark = 'Đã xuất bởi ZenTao';
$lang->execution->burnXUnit = '(Date)';
+5
View File
@@ -80,6 +80,11 @@ $lang->execution->product = $lang->execution->products;
$lang->execution->readjustTime = "调整{$lang->executionCommon}起止时间";
$lang->execution->readjustTask = '顺延任务的起止时间';
$lang->execution->effort = '日志';
$lang->execution->storyEstimate = '需求估算';
$lang->execution->newEstimate = '新一轮估算';
$lang->execution->reestimate = '重新估算';
$lang->execution->selectRound = '选择轮次';
$lang->execution->average = '平均值';
$lang->execution->relatedMember = '相关成员';
$lang->execution->watermark = '由禅道导出';
$lang->execution->burnXUnit = '(日期)';
+6 -1
View File
@@ -196,7 +196,7 @@
<th title='<?php echo $lang->story->taskCount?>' class='w-30px'><?php echo $lang->story->taskCountAB;?></th>
<th title='<?php echo $lang->story->bugCount?>' class='w-30px'><?php echo $lang->story->bugCountAB;?></th>
<th title='<?php echo $lang->story->caseCount?>' class='w-30px'><?php echo $lang->story->caseCountAB;?></th>
<th class='c-actions-4 text-center {sorter:false}'><?php echo $lang->actions;?></th>
<th class='c-actions-5 text-center {sorter:false}'><?php echo $lang->actions;?></th>
</tr>
</thead>
<tbody id='storyTableList' class='sortable'>
@@ -276,6 +276,11 @@
echo html::a($this->createLink('testcase', 'create', "productID=$story->product&branch=$story->branch&moduleID=$story->module&form=&param=0&storyID=$story->id"), '<i class="icon-testcase-create icon-sitemap"></i>', '', "title='{$lang->testcase->create}' data-app='qa'");
}
if($canBeChanged and common::hasPriv('execution', 'storyEstimate', $execution))
{
common::printIcon('execution', 'storyEstimate', "executionID=$execution->id&storyID=$story->id", '', 'list', 'bell', '', 'iframe', true, "data-width='600px'");
}
if($canBeChanged and common::hasPriv('execution', 'unlinkStory', $execution))
{
common::printIcon('execution', 'unlinkStory', "executionID=$execution->id&storyID=$story->id&confirm=no", '', 'list', 'unlink', 'hiddenwin');
@@ -0,0 +1,82 @@
<?php
/**
* The create storyestimate view of execution module of ZenTaoPMS.
*
* @copyright Copyright 2009-2021 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @license ZPL (http://zpl.pub/page/zplv12.html)
* @author Liyuchun <liyuchun@cnezsoft.com>
* @package execution
* @version $Id
* @link http://www.zentao.net
*/
?>
<style>#showAverage {margin: 0;}</style>
<?php include '../../common/view/header.html.php';?>
<?php js::set('executionID', $executionID);?>
<?php js::set('storyID', $storyID);?>
<div id='mainContent' class='main-content'>
<div class="main-header">
<h2><?php echo $lang->execution->storyEstimate;?></h2>
</div>
<div class='btn-toolbar pull-left'>
<?php if(!empty($rounds)):?>
<div class='input-group space w-200px'>
<span class='input-group-addon'><?php echo $lang->execution->selectRound?></span>
<?php echo html::select('round', $rounds, $round, "class='form-control chosen' onchange='selectRound(this.value)'");?>
</div>
<div id='reestimate' class='input-group space w-100px'>
<?php echo html::a("javascript:showNewEstimate()", $lang->execution->reestimate, '', 'class="btn btn-primary"');?>
</div>
<?php endif;?>
</div>
<form class='main-form form-ajax' method='post' target='hiddenwin' id="estimateForm">
<table class='table table-form'>
<thead>
<tr class='text-center'>
<th class='w-120px'><?php echo $lang->execution->team;?></th>
<th class='w-90px'> <?php echo $lang->story->estimate;?></th>
<th class='w-90px th-new-estimate hide'><?php echo $lang->execution->newEstimate;?></th>
</tr>
</thead>
<tbody>
<?php if(!empty($estimateInfo->estimate)):?>
<?php foreach($estimateInfo->estimate as $estimate):?>
<tr class='text-center'>
<td><?php echo zget($users, $estimate->account);?></td>
<?php echo html::hidden('account[]', $estimate->account, "class='form-control'");?>
<td><?php echo $estimate->estimate;?></td>
<td class='new-estimate hide'><?php echo html::input('estimate[]', '', "class='form-control'");?></td>
</tr>
<?php endforeach;?>
<tr class='text-center'>
<td><?php echo $lang->execution->average;?></td>
<td><?php echo $estimateInfo->average;?></td>
<td class='new-estimate hide'><p id='showAverage'></p></td>
<?php echo html::hidden('average', '', "class='form-control'");?>
</tr>
<?php else:?>
<?php foreach($team as $user):?>
<tr class='text-center'>
<td><?php echo zget($users, $user->account);?></td>
<?php echo html::hidden('account[]', $user->account, "class='form-control'");?>
<td class='new-estimate'><?php echo html::input('estimate[]', '', "class='form-control'");?></td>
</tr>
<?php endforeach;?>
<tr class='text-center'>
<td><strong><?php echo $lang->execution->average;?></strong></td>
<td><p id='showAverage'></p></td>
<?php echo html::hidden('average', '', "class='form-control'");?>
</tr>
<?php endif;?>
</tbody>
<tfoot>
<tr>
<td colspan='3' class="text-center form-actions <?php if(!empty($rounds)) echo 'hide';?>">
<?php echo html::submitButton(); ?>
</td>
</tr>
</tfoot>
</table>
</form>
</div>
<?php include '../../common/view/footer.html.php';?>
+2
View File
@@ -585,6 +585,7 @@ $lang->resource->execution->storySort = 'storySort';
$lang->resource->execution->whitelist = 'whitelist';
$lang->resource->execution->addWhitelist = 'addWhitelist';
$lang->resource->execution->unbindWhitelist = 'unbindWhitelist';
$lang->resource->execution->storyEstimate = 'storyEstimate';
//if($config->systemMode == 'classic') $lang->resource->project->list = 'list';
//$lang->execution->methodOrder[0] = 'index';
@@ -640,6 +641,7 @@ $lang->execution->methodOrder[240] = 'storySort';
$lang->execution->methodOrder[245] = 'whitelist';
$lang->execution->methodOrder[250] = 'addWhitelist';
$lang->execution->methodOrder[255] = 'unbindWhitelist';
$lang->execution->methodOrder[260] = 'storyEstimate';
/* Task. */
$lang->resource->task = new stdclass();
+1
View File
@@ -82,6 +82,7 @@ $lang->story->skipStory = '%s is a parent story. It cannot be closed.';
$lang->story->closedStory = 'Story %s is closed and will not be closed.';
$lang->story->batchToTaskTips = "This action will create a task with the same name as the selected {$lang->SRCommon} and link {$lang->SRCommon} to the task. The closed {$lang->SRCommon} will not be converted into tasks.";
$lang->story->successToTask = "Converted to task.";
$lang->story->storyRound = '%s time estimation';
$lang->story->common = 'Story';
$lang->story->id = 'ID';
+1
View File
@@ -83,6 +83,7 @@ $lang->story->skipStory = '%s is a parent story. It cannot be closed.';
$lang->story->closedStory = 'Story %s is closed and will not be closed.';
$lang->story->batchToTaskTips = "This action will create a task with the same name as the selected {$lang->SRCommon} and link {$lang->SRCommon} to the task. The closed {$lang->SRCommon} will not be converted into tasks.";
$lang->story->successToTask = "Converted to task.";
$lang->story->storyRound = '%s time estimation';
$lang->story->common = 'Story';
$lang->story->id = 'ID';
+1
View File
@@ -82,6 +82,7 @@ $lang->story->skipStory = '%s is a parent story. It cannot be closed.';
$lang->story->closedStory = 'Story %s is closed and will not be closed.';
$lang->story->batchToTaskTips = "This action will create a task with the same name as the selected {$lang->SRCommon} and link {$lang->SRCommon} to the task. The closed {$lang->SRCommon} will not be converted into tasks.";
$lang->story->successToTask = "Converted to task.";
$lang->story->storyRound = '%s time estimation';
$lang->story->common = 'Story';
$lang->story->id = 'ID';
+1
View File
@@ -82,6 +82,7 @@ $lang->story->skipStory = '%s is a parent story. It cannot be closed.';
$lang->story->closedStory = 'Story %s is closed and will not be closed.';
$lang->story->batchToTaskTips = "This action will create a task with the same name as the selected {$lang->SRCommon} and link {$lang->SRCommon} to the task. The closed {$lang->SRCommon} will not be converted into tasks.";
$lang->story->successToTask = "Converted to task.";
$lang->story->storyRound = '%s time estimation';
$lang->story->common = 'Câu chuyện';
$lang->story->id = 'ID';
+1
View File
@@ -83,6 +83,7 @@ $lang->story->skipStory = '需求:%s 为父需求,将不会被关闭
$lang->story->closedStory = '需求:%s 已关闭,将不会被关闭。';
$lang->story->batchToTaskTips = "此操作会创建与所选{$lang->SRCommon}同名的任务,并将{$lang->SRCommon}关联到任务中,已关闭的需求不会转为任务。";
$lang->story->successToTask = '批量转任务成功';
$lang->story->storyRound = '第 %s 轮估算';
$lang->story->common = $lang->SRCommon;
$lang->story->id = '编号';
+78
View File
@@ -3938,6 +3938,84 @@ class storyModel extends model
return $relations;
}
/**
* Get estimate info
*
* @param int $storyID
* @param int $round
* @access public
* @return bool|array
*/
public function getEstimateInfo($storyID, $round = 0)
{
$estimateInfo = $this->dao->select('*')->from(TABLE_STORYESTIMATE)
->where('story')->eq($storyID)
->beginIf($round)->andWhere('round')->eq($round)->fi()
->orderBy('round_desc')
->fetch();
if(!empty($estimateInfo)) $estimateInfo->estimate = json_decode($estimateInfo->estimate);
return $estimateInfo;
}
/**
* Get estimate rounds.
*
* @param int $storyID
* @access public
* @return array
*/
public function getEstimateRounds($storyID)
{
$lastRound = $this->dao->select('round')->from(TABLE_STORYESTIMATE)
->where('story')->eq($storyID)
->orderBy('round_desc')
->fetch('round');
if(!$lastRound) return array();
$rounds = array();
for($i = 1; $i <= $lastRound; $i++)
{
$rounds[$i] = sprintf($this->lang->story->storyRound, $i);
}
return $rounds;
}
/**
* Save estimate information.
*
* @param int $storyID
* @access public
* @return void
*/
public function saveEstimateInfo($storyID)
{
$data = fixer::input('post')->get();
$lastRound = $this->dao->select('round')->from(TABLE_STORYESTIMATE)
->where('story')->eq($storyID)
->orderBy('round_desc')
->fetch('round');
$estimates = array();
foreach($data->account as $key => $account)
{
$estimates[$key]['account'] = $account;
$estimates[$key]['estimate'] = $data->estimate[$key];
}
$storyEstimate = new stdclass();
$storyEstimate->story = $storyID;
$storyEstimate->round = empty($lastRound) ? 1 : $lastRound + 1;
$storyEstimate->estimate = json_encode($estimates);
$storyEstimate->average = $data->average;
$storyEstimate->openedBy = $this->app->user->account;
$storyEstimate->openedDate = helper::now();
$this->dao->insert(TABLE_STORYESTIMATE)->data($storyEstimate)->exec();
}
/**
* Update the story order according to the plan.
*