* Finish task #38180.
This commit is contained in:
@@ -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`');
|
||||
|
||||
@@ -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`;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)';
|
||||
|
||||
@@ -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)';
|
||||
|
||||
@@ -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)';
|
||||
|
||||
@@ -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)';
|
||||
|
||||
@@ -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 = '(日期)';
|
||||
|
||||
@@ -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=¶m=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';?>
|
||||
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = '编号';
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user