Merge branch 'zentaopms_256'

This commit is contained in:
Yagami
2022-12-05 10:36:22 +08:00
81 changed files with 2946 additions and 1290 deletions
+3
View File
@@ -0,0 +1,3 @@
ALTER TABLE `zt_story` ADD COLUMN `siblings` varchar(255) NOT NULL AFTER `linkRequirements`;
ALTER TABLE `zt_productplan` MODIFY COLUMN `branch` varchar(255) NOT NULL DEFAULT '0';
+2 -1
View File
@@ -1129,7 +1129,7 @@ CREATE TABLE IF NOT EXISTS `zt_product` (
CREATE TABLE IF NOT EXISTS `zt_productplan` (
`id` mediumint(8) unsigned NOT NULL auto_increment,
`product` mediumint(8) unsigned NOT NULL,
`branch` mediumint(8) unsigned NOT NULL,
`branch` varchar(255) NOT NULL DEFAULT '0',
`parent` mediumint(9) NOT NULL DEFAULT '0',
`title` varchar(90) NOT NULL,
`status` enum('wait','doing','done','closed') NOT NULL default 'wait',
@@ -1480,6 +1480,7 @@ CREATE TABLE IF NOT EXISTS `zt_story` (
`childStories` varchar(255) NOT NULL,
`linkStories` varchar(255) NOT NULL,
`linkRequirements` varchar(255) NOT NULL,
`siblings` varchar(255) NOT NULL,
`duplicateStory` mediumint(8) unsigned NOT NULL,
`version` smallint(6) NOT NULL default '1',
`feedbackBy` varchar(100) NOT NULL,
+3
View File
@@ -225,6 +225,7 @@ $lang->action->desc->reopen = '$date, reopened by <strong>$actor</
$lang->action->desc->merged = '$date, merged by <strong>$actor</strong> .' . "\n";
$lang->action->desc->submitreview = '$date, submitted for review by <strong>$actor</strong>.' . "\n";
$lang->action->desc->ganttmove = '$date, sort by <strong>$actor</strong> .' . "\n";
$lang->action->desc->relieved = '$date, relieved by <strong>$actor</strong> .' . "\n";
$lang->action->desc->switchtolight = '$date, Switch from ALM mode to light mode by <strong>'. $lang->admin->system .'</strong>.' . "\n";
$lang->action->desc->unlinkproduct = '$date, the project is disassociated from the $extra, synchronization disassociates the ' . $lang->executionCommon . 's of the project from the $extra.' . "\n";
@@ -395,6 +396,8 @@ $lang->action->label->tolib = 'imported';
$lang->action->label->updatetolib = 'updated';
$lang->action->label->ganttmove = 'sorted';
$lang->action->label->submitreview = 'submitted';
$lang->action->label->syncsiblings = 'synchronized changes';
$lang->action->label->relieved = 'relieved';
$lang->action->label->switchtolight = 'switch from ALM mode to light mode';
$lang->action->label->linkedrepo = 'Linked Code Repo';
$lang->action->label->unlinkedrepo = 'Unlinked Code Repo';
+3
View File
@@ -225,6 +225,7 @@ $lang->action->desc->reopen = '$date, reopened by <strong>$actor</
$lang->action->desc->merged = '$date, merged by <strong>$actor</strong> .' . "\n";
$lang->action->desc->submitreview = '$date, submitted for review by <strong>$actor</strong>.' . "\n";
$lang->action->desc->ganttmove = '$date, sort by <strong>$actor</strong> .' . "\n";
$lang->action->desc->relieved = '$date, relieved by <strong>$actor</strong> .' . "\n";
$lang->action->desc->switchtolight = '$date, Switch from ALM mode to light mode by <strong>'. $lang->admin->system .'</strong>.' . "\n";
$lang->action->desc->unlinkproduct = '$date, the project is disassociated from the $extra, synchronization disassociates the ' . $lang->executionCommon . 's of the project from the $extra.' . "\n";
@@ -395,6 +396,8 @@ $lang->action->label->tolib = 'imported';
$lang->action->label->updatetolib = 'updated';
$lang->action->label->ganttmove = 'sorted';
$lang->action->label->submitreview = 'submitted';
$lang->action->label->syncsiblings = 'synchronized changes';
$lang->action->label->relieved = 'relieved';
$lang->action->label->switchtolight = 'switch from ALM mode to light mode';
$lang->action->label->linkedrepo = 'Linked Code Repo';
$lang->action->label->unlinkedrepo = 'Unlinked Code Repo';
+3
View File
@@ -225,6 +225,7 @@ $lang->action->desc->reopen = '$date, reopened by <strong>$actor</
$lang->action->desc->merged = '$date, merged by <strong>$actor</strong> .' . "\n";
$lang->action->desc->submitreview = '$date, submitted for review by <strong>$actor</strong>.' . "\n";
$lang->action->desc->ganttmove = '$date, sort by <strong>$actor</strong> .' . "\n";
$lang->action->desc->relieved = '$date, relieved by <strong>$actor</strong> .' . "\n";
$lang->action->desc->switchtolight = '$date, Switch from ALM mode to light mode by <strong>'. $lang->admin->system .'</strong>.' . "\n";
$lang->action->desc->unlinkproduct = '$date, the project is disassociated from the $extra, synchronization disassociates the ' . $lang->executionCommon . 's of the project from the $extra.' . "\n";
@@ -395,6 +396,8 @@ $lang->action->label->tolib = 'Importé';
$lang->action->label->updatetolib = 'MàJ';
$lang->action->label->ganttmove = 'sorted';
$lang->action->label->submitreview = 'submitted';
$lang->action->label->syncsiblings = 'synchronized changes';
$lang->action->label->relieved = 'relieved';
$lang->action->label->switchtolight = 'switch from ALM mode to light mode';
$lang->action->label->linkedrepo = 'Linked Code Repo';
$lang->action->label->unlinkedrepo = 'Unlinked Code Repo';
+1
View File
@@ -306,6 +306,7 @@ $lang->action->label->syncproject = 'start';
$lang->action->label->syncexecution = 'start';
$lang->action->label->startProgram = '(The start of the project sets the status of the program as Ongoing)';
$lang->action->label->submitreview = 'submitted';
$lang->action->label->syncsiblings = 'synchronized changes';
$lang->action->label->linkedrepo = 'Linked Code Repo';
$lang->action->label->unlinkedrepo = 'Unlinked Code Repo';
+3
View File
@@ -225,6 +225,7 @@ $lang->action->desc->reopen = '$date, 由 <strong>$actor</strong>
$lang->action->desc->merged = '$date, 由 <strong>$actor</strong> 合并。' . "\n";
$lang->action->desc->submitreview = '$date, 由 <strong>$actor</strong> 提交评审。' . "\n";
$lang->action->desc->ganttmove = '$date, 由 <strong>$actor</strong> 排序。' . "\n";
$lang->action->desc->relieved = '$date, 由 <strong>$actor</strong> 解除孪生需求关系。' . "\n";
$lang->action->desc->switchtolight = '$date, 由于 <strong>'. $lang->admin->system .'</strong> 从全生命周期管理模式切换为轻量管理模式,项目访问控制由项目集内公开调整为私有。' . "\n";
$lang->action->desc->unlinkproduct = '$date, 系统判断由于' . $lang->executionCommon . '所属项目与$extra取消关联,同步将' . $lang->executionCommon . '与$extra取消关联。' . "\n";
@@ -395,6 +396,8 @@ $lang->action->label->tolib = '导入了';
$lang->action->label->updatetolib = '更新了';
$lang->action->label->ganttmove = '排序了';
$lang->action->label->submitreview = '提交了评审';
$lang->action->label->syncsiblings = '同步修改了';
$lang->action->label->relieved = '解除了';
$lang->action->label->switchtolight = '从全生命周期管理模式切换为轻量管理模式';
$lang->action->label->linkedrepo = '关联代码库到';
$lang->action->label->unlinkedrepo = '取消了项目与代码库的关联';
+12 -1
View File
@@ -50,7 +50,7 @@ class actionModel extends model
$action->extra = $extra;
if(!defined('IN_UPGRADE')) $action->vision = $this->config->vision;
if($objectType == 'story' and strpos(',reviewpassed,reviewrejected,reviewclarified,reviewreverted,', ",$actionType,") !== false) $action->actor = $this->lang->action->system;
if($objectType == 'story' and strpos(',reviewpassed,reviewrejected,reviewclarified,reviewreverted,syncsiblings,', ",$actionType,") !== false) $action->actor = $this->lang->action->system;
/* Use purifier to process comment. Fix bug #2683. */
$action->comment = fixer::stripDataTags($comment);
@@ -952,6 +952,17 @@ class actionModel extends model
}
}
if($action->objectType == 'story' and $action->action == 'syncsiblings')
{
if(!empty($extra) and strpos($extra, '|') !== false)
{
list($operate, $storyID) = explode('|', $extra);
$desc['operate'] = $this->lang->$objectType->{$desc['operate']};
$link = common::hasPriv('story', 'view') ? html::a(helper::createLink('story', 'view', "storyID=$storyID"), "#$storyID ") : "#$storyID";
$actionDesc = str_replace(array('$extra', '$operate'), array($link, $desc['operate'][$operate]), $desc['main']);
}
}
if($action->objectType == 'module' and strpos(',created,moved,', $action->action) !== false)
{
$moduleNames = $this->loadModel('tree')->getOptionMenu($action->objectID, 'story', 0, 'all', '');
+4 -1
View File
@@ -260,10 +260,12 @@ class branch extends control
* @param string $param
* @param int $projectID
* @param bool $withMainBranch
* @param string $isSiblings
* @param string $fieldID
* @access public
* @return void
*/
public function ajaxGetBranches($productID, $oldBranch = 0, $param = 'all', $projectID = 0, $withMainBranch = true)
public function ajaxGetBranches($productID, $oldBranch = 0, $param = 'all', $projectID = 0, $withMainBranch = true, $isSiblings = 'no', $fieldID = '0')
{
$product = $this->loadModel('product')->getById($productID);
if(empty($product) or $product->type == 'normal') return;
@@ -280,6 +282,7 @@ class branch extends control
$branchTagOption[$oldBranch] = $oldBranch == BRANCH_MAIN ? $branch : ($branch->name . ($branch->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : ''));
}
if($isSiblings == 'yes') return print(html::select("branches[$fieldID]", $branchTagOption, $oldBranch, "onchange='loadBranchRelation(this.value, $fieldID);' class='form-control chosen control-branch'"));
return print(html::select('branch', $branchTagOption, $oldBranch, "class='form-control' onchange='loadBranch(this)' data-last='{$oldBranch}'"));
}
+44 -23
View File
@@ -898,7 +898,7 @@ class execution extends control
$storyBugs = $this->loadModel('bug')->getStoryBugCounts($storyIdList, $executionID);
$storyCases = $this->loadModel('testcase')->getStoryCaseCounts($storyIdList);
$plans = $this->execution->getPlans($products, 'skipParent|withMainPlan');
$plans = $this->execution->getPlans($products, 'skipParent|withMainPlan', $executionID);
$allPlans = array('' => '');
if(!empty($plans))
{
@@ -1645,7 +1645,20 @@ class execution extends control
}
else
{
return print(js::confirm($this->lang->execution->importPlanStory, inlink('create', "projectID=$projectID&executionID=$executionID&copyExecutionID=&planID=$planID&confirm=yes"), inlink('create', "projectID=$projectID&executionID=$executionID")));
$executionProductList = $this->loadModel('product')->getProducts($executionID);
$multiBranchProduct = false;
foreach($executionProductList as $executionProduct)
{
if($executionProduct->type != 'normal')
{
$multiBranchProduct = true;
break;
}
}
$importPlanStoryTips = $multiBranchProduct ? $this->lang->execution->importBranchPlanStory : $this->lang->execution->importPlanStory;
return print(js::confirm($importPlanStoryTips, inlink('create', "projectID=$projectID&executionID=$executionID&copyExecutionID=&planID=$planID&confirm=yes"), inlink('create', "projectID=$projectID&executionID=$executionID")));
}
}
@@ -1689,10 +1702,11 @@ class execution extends control
$linkedBranches = array();
foreach($products as $productIndex => $product)
{
$productPlans[$productIndex] = array();
foreach($branches[$productIndex] as $branchID => $branch)
{
$linkedBranches[$productIndex][$branchID] = $branchID;
$productPlans[$productIndex][$branchID] = isset($plans[$productIndex][$branchID]) ? $plans[$productIndex][$branchID] : array();
$productPlans[$productIndex] += isset($plans[$productIndex][$branchID]) ? $plans[$productIndex][$branchID] : array();
}
}
@@ -1724,10 +1738,11 @@ class execution extends control
$linkedBranches = array();
foreach($products as $productIndex => $product)
{
$productPlans[$productIndex] = array();
foreach($branches[$productIndex] as $branchID => $branch)
{
$linkedBranches[$productIndex][$branchID] = $branchID;
$productPlans[$productIndex][$branchID] = isset($plans[$productIndex][$branchID]) ? $plans[$productIndex][$branchID] : array();
$productPlans[$productIndex] += isset($plans[$productIndex][$branchID]) ? $plans[$productIndex][$branchID] : array();
}
}
@@ -1776,27 +1791,15 @@ class execution extends control
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('doc', 'objectLibs', "type=execution")));
}
$planID = '';
if(isset($_POST['plans']))
{
foreach($_POST['plans'] as $plans)
{
foreach($plans as $planID)
{
if(!empty($planID)) break;
}
}
}
if(!empty($projectID) and $project->model == 'kanban')
{
$execution = $this->execution->getById($executionID);
$this->loadModel('kanban')->createRDKanban($execution);
}
if(!empty($planID))
if(!empty($_POST['plans']))
{
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('create', "projectID=$projectID&executionID=$executionID&copyExecutionID=&planID=$planID&confirm=no")));
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('create', "projectID=$projectID&executionID=$executionID&copyExecutionID=&planID=1&confirm=no")));
}
else
{
@@ -1904,7 +1907,20 @@ class execution extends control
}
elseif(!empty($newPlans))
{
return print(js::confirm($this->lang->execution->importEditPlanStory, inlink('edit', "executionID=$executionID&action=edit&extra=&newPlans=$newPlans&confirm=yes"), inlink('view', "executionID=$executionID")));
$executionProductList = $this->loadModel('product')->getProducts($executionID);
$multiBranchProduct = false;
foreach($executionProductList as $executionProduct)
{
if($executionProduct->type != 'normal')
{
$multiBranchProduct = true;
break;
}
}
$importEditPlanStoryTips = $multiBranchProduct ? $this->lang->execution->importBranchEditPlanStory : $this->lang->execution->importEditPlanStory;
return print(js::confirm($importEditPlanStoryTips, inlink('edit', "executionID=$executionID&action=edit&extra=&newPlans=$newPlans&confirm=yes"), inlink('view', "executionID=$executionID")));
}
/* Set menu. */
@@ -2003,12 +2019,15 @@ class execution extends control
foreach($linkedProducts as $productID => $linkedProduct)
{
if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->name;
$productPlans[$productID] = array();
foreach($branches[$productID] as $branchID => $branch)
{
$productPlans[$productID] += isset($plans[$productID][$branchID]) ? $plans[$productID][$branchID] : array();
$linkedBranchList[$branchID] = $branchID;
$linkedBranches[$productID][$branchID] = $branchID;
$productPlans[$productID][$branchID] = isset($plans[$productID][$branchID]) ? $plans[$productID][$branchID] : array();
if($branchID != BRANCH_MAIN and isset($plans[$productID][BRANCH_MAIN])) $productPlans[$productID][$branchID] += $plans[$productID][BRANCH_MAIN];
if($branchID != BRANCH_MAIN and isset($plans[$productID][BRANCH_MAIN])) $productPlans[$productID] += $plans[$productID][BRANCH_MAIN];
if(!empty($executionStories[$productID][$branchID]))
{
array_push($unmodifiableProducts, $productID);
@@ -4121,9 +4140,11 @@ class execution extends control
$count = 0;
if(!empty($planStory))
{
$projectProducts = $this->loadModel('project')->getBranchesByProject($executionID);
foreach($planStory as $id => $story)
{
if($story->status == 'draft' or $story->status == 'reviewing')
$projectBranches = zget($projectProducts, $story->product, array());
if($story->status == 'draft' or $story->status == 'reviewing' or (!empty($story->branch) and !empty($projectBranches) and !isset($projectBranches[$story->branch])))
{
$count++;
unset($planStory[$id]);
@@ -4156,7 +4177,7 @@ class execution extends control
}
$haveDraft = sprintf($this->lang->execution->haveDraft, $count);
if(!$execution->multiple) $haveDraft = str_replace($this->lang->executionCommon, $this->lang->projectCommon, $haveDraft);
if(!$execution->multiple or $moduleName == 'projectstory') $haveDraft = str_replace($this->lang->executionCommon, $this->lang->projectCommon, $haveDraft);
if($count != 0) echo js::alert($haveDraft) . js::locate($this->createLink($moduleName, $fromMethod, $param));
return print(js::locate(helper::createLink($moduleName, $fromMethod, $param)));
}
+9 -3
View File
@@ -21,9 +21,9 @@
.chosen-container.chosen-highlight-selected .disabled-result.result-selected {background: none;}
#productsBox .row .col-sm-4 {padding-right: 13px;}
#plansBox .row {display: inline-table; width: 102%;}
#plansBox .row .col-sm-4 {display: inline-block; float: none; padding-right: 13px;}
.productsBox .row .col-sm-4 {padding-right: 13px;}
.plansBox .row {display: inline-table; width: 102%;}
.plansBox .row .col-sm-4 {display: inline-block; float: none; padding-right: 13px;}
#dateRange {vertical-align: top; padding-top: 13px;}
#dateRangeOption {vertical-align: top; padding-top: 13px;}
@@ -32,3 +32,9 @@
#copyProjectModal .projectSelect {display: inline-block; margin-left: 10px; width: 20%;}
#copyProjectModal .titleBox{position: relative; bottom: 3px; display: inline-block;}
a.chosen-single > div > b {top: -4px !important;}
.productsBox a {border-radius: 2px !important;}
.productsBox .required:after {right: -9.5px !important;}
.productsBox .input-group-addon > div {display: inline-block !important;}
.productsBox .required + .text-danger.help-text {position: relative; left: 10px;}
.productsBox > #productNameLabel {padding-top: 8px;}
+89 -74
View File
@@ -113,6 +113,15 @@ function computeEndDate(delta)
computeWorkDays();
}
/* Auto compute the work days. */
$(function()
{
$(".date").bind('dateSelected', function()
{
computeWorkDays(this.id);
})
});
/**
* Load branches.
*
@@ -122,118 +131,127 @@ function computeEndDate(delta)
*/
function loadBranches(product)
{
if($('#productsBox .input-group:last select:first').val() != 0)
/* When selecting a product, delete a plan that is empty by default. */
$("#planDefault").remove();
$(".productsBox select[name^='products']").each(function()
{
if(model !== 'waterfall')
var $product = $(product);
var productID = $(this).val();
if($product.val() != 0 && $product.val() == $(this).val() && $product.attr('id') != $(this).attr('id') && !multiBranchProducts[$product.val()])
{
var length = $('#productsBox .input-group').size();
$('#productsBox .row').append('<div class="col-sm-4">' + $('#productsBox .col-sm-4:last').html().replace('required', '') + '</div>');
if($('#productsBox .input-group:last select').size() >= 2) $('#productsBox .input-group:last select:last').remove();
$('#productsBox .input-group:last .chosen-container').remove();
$('#productsBox .input-group:last select:first').attr('name', 'products[' + length + ']').attr('id', 'products' + length);
$('#productsBox .input-group:last .chosen').chosen();
bootbox.alert(errorSameProducts);
$product.val(0);
$product.trigger("chosen:updated");
return false;
}
adjustProductBoxMargin();
});
var $tableRow = $(product).closest('.table-row');
var index = $tableRow.find('select:first').attr('id').replace('products' , '');
var oldBranch = $(product).attr('data-branch') !== undefined ? $(product).attr('data-branch') : 0;
if($(product).val() != 0)
{
$(product).closest('tr').find('.newProduct').addClass('hidden')
}
else
{
$(product).closest('tr').find('.newProduct').removeClass('hidden')
}
var $inputgroup = $(product).closest('.input-group');
if($inputgroup.find('select').size() >= 2) $inputgroup.removeClass('has-branch').find('select:last').remove();
if($inputgroup.find('.chosen-container').size() >= 2) $inputgroup.find('.chosen-container:last').remove();
if(!multiBranchProducts[$(product).val()])
{
$tableRow.find('.table-col:last select').val('').trigger('chosen:updated');
$tableRow.find('.table-col:last').addClass('hidden');
}
var projectID = $('#project').val();
if(typeof(projectID) == 'undefined') projectID = 0;
var index = $inputgroup.find('select:first').attr('id').replace('products' , '');
$.get(createLink('branch', 'ajaxGetBranches', "productID=" + $(product).val() + "&oldBranch=0&param=active&projectID=" + projectID + "&withMainBranch=true"), function(data)
$.get(createLink('branch', 'ajaxGetBranches', "productID=" + $(product).val() + "&oldBranch=" + oldBranch + "&param=active&projectID=" + projectID + "&withMainBranch=true"), function(data)
{
if(data)
{
$inputgroup.addClass('has-branch').append(data);
$inputgroup.find('select:last').attr('name', 'branch[' + index + ']').attr('id', 'branch' + index).attr('onchange', "loadPlans('#products" + index + "', this.value)").chosen();
$tableRow.find("select[name^='branch']").replaceWith(data);
$tableRow.find('.table-col:last .chosen-container').remove();
$tableRow.find('.table-col:last').removeClass('hidden');
$tableRow.find("select[name^='branch']").attr('multiple', '').attr('name', 'branch[' + index + '][]').attr('id', 'branch' + index).attr('onchange', "loadPlans('#products" + index + "', this)").chosen();
$inputgroup.find('select:last').each(disableSelectedBranch);
disableSelectedProduct();
}
var branchID = $('#branch' + index).val();
loadPlans(product, branchID);
var branch = $('#branch' + index);
loadPlans(product, branch);
});
if(!multiBranchProducts[$(product).val()]) disableSelectedProduct();
}
/**
* Load plans by product id.
* Load plans.
*
* @param int $product
* @param int $branchID
* @param obj $product
* @param obj $branchID
* @access public
* @return void
*/
function loadPlans(product, branchID)
function loadPlans(product, branch)
{
if($('#plansBox').size() == 0) return false;
var productID = $(product).val();
var branchID = typeof(branchID) == 'undefined' ? 0 : branchID;
var branchID = $(branch).val() == null ? 0 : '0,' + $(branch).val();
var planID = $(product).attr('data-plan') !== undefined ? $(product).attr('data-plan') : 0;
var index = $(product).attr('id').replace('products', '');
$.get(createLink('product', 'ajaxGetPlans', "productID=" + productID + '&branch=0,' + branchID + '&planID=0&fieldID&needCreate=&expired=noclosed,unexpired&param=skipParent,multiple'), function(data)
$.get(createLink('product', 'ajaxGetPlans', "productID=" + productID + '&branch=' + branchID + '&planID=' + planID + '&fieldID&needCreate=&expired=unexpired,noclosed&param=skipParent,multiple'), function(data)
{
if(data)
{
if($("div#plan" + index).size() == 0) $("#plansBox .row").append('<div class="col-sm-4" id="plan' + index + '"></div>');
$("div#plan" + index).html(data).find('select').attr('name', 'plans[' + productID + '][' + branchID + '][]').attr('id', 'plans' + productID).chosen();
adjustPlanBoxMargin();
$("div#plan" + index).find("select[name^='plans']").replaceWith(data);
$("div#plan" + index).find('.chosen-container').remove();
$("div#plan" + index).find('select').attr('name', 'plans[' + productID + ']' + '[]').attr('id', 'plans' + productID).chosen();
}
});
}
/**
* Adjust product box margin.
*
* Add new line for link product.
*
* @param obj $obj
* @access public
* @return void
*/
function adjustProductBoxMargin()
function addNewLine(obj)
{
var productRows = Math.ceil($('#productsBox > .row > .col-sm-4').length / 3);
if(productRows > 1)
var newLine = $(obj).closest('tr').clone();
var index = 0;
$(".productsBox select[name^='products']").each(function()
{
for(i = 1; i <= productRows - 1; i++)
{
$('#productsBox .col-sm-4:lt(' + (i * 3) + ')').css('margin-bottom', '10px');
}
}
}
var id = $(this).attr('id').replace('products' , '');
/**
* Adjust plan box margin.
*
* @access public
* @return void
*/
function adjustPlanBoxMargin()
{
var planRows = Math.ceil($('#plansBox > .row > .col-sm-4').length / 3);
if(planRows > 1)
{
for(j = 1; j <= planRows - 1; j++)
{
$('#plansBox .col-sm-4:lt(' + (j * 3) + ')').css('margin-bottom', '10px');
}
}
}
id = parseInt(id);
id ++;
/* Auto compute the work days. */
$(function()
{
$(".date").bind('dateSelected', function()
{
computeWorkDays(this.id);
index = id > index ? id : index;
})
});
newLine.find('.newProduct').remove();
newLine.find('.addProduct').remove();
newLine.addClass('newLine');
newLine.find('th').html('');
newLine.find('.removeLine').css('visibility', 'visible');
newLine.find('.chosen-container').remove();
newLine.find('.productsBox .table-col:last').addClass('hidden');
newLine.find("select[name^='products']").attr('name', 'products[' + index + ']').attr('id', 'products' + index).val('').chosen();
newLine.find("select[name^='plans']").attr('name', 'plans[' + index + '][' + 0 + '][]').chosen();
newLine.find("div[id^='plan']").attr('id', 'plan' + index);
$(obj).closest('tr').after(newLine);
var product = newLine.find("select[name^='products']");
var branch = newLine.find("select[name^='branch']");
loadPlans(product, branch);
disableSelectedProduct();
}
function removeLine(obj)
{
$(obj).closest('tr').remove();
disableSelectedProduct();
}
$(function()
{
@@ -247,9 +265,6 @@ $(function()
e.stopPropagation();
e.preventDefault();
});
adjustProductBoxMargin();
adjustPlanBoxMargin();
});
/**
+79 -77
View File
@@ -209,82 +209,84 @@ $lang->execution->burnByList['estimate'] = "View by plan hours";
$lang->execution->burnByList['storyPoint'] = 'View by story point';
/* Method list. */
$lang->execution->index = "Home";
$lang->execution->task = 'Aufgaben';
$lang->execution->groupTask = 'Nach Gruppen';
$lang->execution->story = 'Storys';
$lang->execution->qa = 'QA';
$lang->execution->bug = 'Bugs';
$lang->execution->testcase = 'Testcase List';
$lang->execution->dynamic = 'Verlauf';
$lang->execution->latestDynamic = 'Letzter Verlauf';
$lang->execution->build = 'Builds';
$lang->execution->testtask = 'Testaufgaben';
$lang->execution->burn = 'Burndown';
$lang->execution->computeBurn = 'Aktualisieren';
$lang->execution->CFD = 'Cumulative Flow diagrams';
$lang->execution->computeCFD = 'Compute Cumulative Flow diagrams';
$lang->execution->burnData = 'Burndown Daten';
$lang->execution->fixFirst = 'Bearbeite Mannstunden des ersten Tags';
$lang->execution->team = 'Teammitglieder';
$lang->execution->doc = 'Dok';
$lang->execution->doclib = 'Dok Bibliothek';
$lang->execution->manageProducts = 'Verküpfe ' . $lang->productCommon;
$lang->execution->linkStory = 'Link Stories';
$lang->execution->linkStoryByPlan = 'Verküpfe Story aus Plan';
$lang->execution->linkPlan = 'Verküpfe Plan';
$lang->execution->unlinkStoryTasks = 'Verknüpfung aufheben';
$lang->execution->linkedProducts = 'Verküpfte Produkte';
$lang->execution->unlinkedProducts = 'Produkt verknüpfung aufheben';
$lang->execution->view = "Übersicht";
$lang->execution->startAction = "Start Execution";
$lang->execution->activateAction = "Activate Execution";
$lang->execution->delayAction = "Delay Execution";
$lang->execution->suspendAction = "Suspend Execution";
$lang->execution->closeAction = "Close Execution";
$lang->execution->testtaskAction = "Execution Request";
$lang->execution->teamAction = "Execution Members";
$lang->execution->kanbanAction = "Execution Kanban";
$lang->execution->printKanbanAction = "Print Kanban";
$lang->execution->treeAction = "Execution Tree View";
$lang->execution->exportAction = "Export Execution";
$lang->execution->computeBurnAction = "Compute Burn";
$lang->execution->create = "Erstelle Projekt";
$lang->execution->createExec = "Create Execution";
$lang->execution->createAction = "Create {$lang->execution->common}";
$lang->execution->copyExec = "Copy Execution";
$lang->execution->copy = "Kopiere {$lang->executionCommon}";
$lang->execution->delete = "Lösche";
$lang->execution->deleteAB = "Lösche Execution";
$lang->execution->browse = "Durchsuchen";
$lang->execution->edit = "Bearbeiten";
$lang->execution->editAction = "Edit Execution";
$lang->execution->batchEdit = "Mehere bearbeiten";
$lang->execution->batchEditAction = "Batch Edit";
$lang->execution->manageMembers = 'Teams verwalten';
$lang->execution->unlinkMember = 'Mitgliefer entfernen';
$lang->execution->unlinkStory = 'Story entfernen';
$lang->execution->unlinkStoryAB = 'Unlink';
$lang->execution->batchUnlinkStory = 'Mehere Storys entfernen';
$lang->execution->importTask = 'Importiere Aufgaben';
$lang->execution->importPlanStories = 'Verknüpfe Story aus Plan';
$lang->execution->importBug = 'Importiere Bugs';
$lang->execution->tree = 'Baum';
$lang->execution->treeTask = 'Aufgabe anzeigen';
$lang->execution->treeStory = 'Story anzeigen';
$lang->execution->treeViewTask = 'Tree View Task';
$lang->execution->treeViewStory = 'Tree View Story';
$lang->execution->storyKanban = 'Story Kanban';
$lang->execution->storySort = 'Story sortieren';
$lang->execution->importPlanStory = '' . $lang->executionCommon . ' wurde erstellt!\nMöchten Sie Storys aus dem Plan importieren?';
$lang->execution->importEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
$lang->execution->needLinkProducts = 'The execution has not been linked with any product, and the related functions cannot be used. Please link the product first and try again.';
$lang->execution->iteration = 'Iteration';
$lang->execution->iterationInfo = '%s Iterationen';
$lang->execution->viewAll = 'Alle anzeigen';
$lang->execution->testreport = 'Test Report';
$lang->execution->taskKanban = 'Task Kanban';
$lang->execution->RDKanban = 'Research & Development Kanban';
$lang->execution->index = "Home";
$lang->execution->task = 'Aufgaben';
$lang->execution->groupTask = 'Nach Gruppen';
$lang->execution->story = 'Storys';
$lang->execution->qa = 'QA';
$lang->execution->bug = 'Bugs';
$lang->execution->testcase = 'Testcase List';
$lang->execution->dynamic = 'Verlauf';
$lang->execution->latestDynamic = 'Letzter Verlauf';
$lang->execution->build = 'Builds';
$lang->execution->testtask = 'Testaufgaben';
$lang->execution->burn = 'Burndown';
$lang->execution->computeBurn = 'Aktualisieren';
$lang->execution->CFD = 'Cumulative Flow diagrams';
$lang->execution->computeCFD = 'Compute Cumulative Flow diagrams';
$lang->execution->burnData = 'Burndown Daten';
$lang->execution->fixFirst = 'Bearbeite Mannstunden des ersten Tags';
$lang->execution->team = 'Teammitglieder';
$lang->execution->doc = 'Dok';
$lang->execution->doclib = 'Dok Bibliothek';
$lang->execution->manageProducts = 'Verküpfe ' . $lang->productCommon;
$lang->execution->linkStory = 'Link Stories';
$lang->execution->linkStoryByPlan = 'Verküpfe Story aus Plan';
$lang->execution->linkPlan = 'Verküpfe Plan';
$lang->execution->unlinkStoryTasks = 'Verknüpfung aufheben';
$lang->execution->linkedProducts = 'Verküpfte Produkte';
$lang->execution->unlinkedProducts = 'Produkt verknüpfung aufheben';
$lang->execution->view = "Übersicht";
$lang->execution->startAction = "Start Execution";
$lang->execution->activateAction = "Activate Execution";
$lang->execution->delayAction = "Delay Execution";
$lang->execution->suspendAction = "Suspend Execution";
$lang->execution->closeAction = "Close Execution";
$lang->execution->testtaskAction = "Execution Request";
$lang->execution->teamAction = "Execution Members";
$lang->execution->kanbanAction = "Execution Kanban";
$lang->execution->printKanbanAction = "Print Kanban";
$lang->execution->treeAction = "Execution Tree View";
$lang->execution->exportAction = "Export Execution";
$lang->execution->computeBurnAction = "Compute Burn";
$lang->execution->create = "Erstelle Projekt";
$lang->execution->createExec = "Create Execution";
$lang->execution->createAction = "Create {$lang->execution->common}";
$lang->execution->copyExec = "Copy Execution";
$lang->execution->copy = "Kopiere {$lang->executionCommon}";
$lang->execution->delete = "Lösche";
$lang->execution->deleteAB = "Lösche Execution";
$lang->execution->browse = "Durchsuchen";
$lang->execution->edit = "Bearbeiten";
$lang->execution->editAction = "Edit Execution";
$lang->execution->batchEdit = "Mehere bearbeiten";
$lang->execution->batchEditAction = "Batch Edit";
$lang->execution->manageMembers = 'Teams verwalten';
$lang->execution->unlinkMember = 'Mitgliefer entfernen';
$lang->execution->unlinkStory = 'Story entfernen';
$lang->execution->unlinkStoryAB = 'Unlink';
$lang->execution->batchUnlinkStory = 'Mehere Storys entfernen';
$lang->execution->importTask = 'Importiere Aufgaben';
$lang->execution->importPlanStories = 'Verknüpfe Story aus Plan';
$lang->execution->importBug = 'Importiere Bugs';
$lang->execution->tree = 'Baum';
$lang->execution->treeTask = 'Aufgabe anzeigen';
$lang->execution->treeStory = 'Story anzeigen';
$lang->execution->treeViewTask = 'Tree View Task';
$lang->execution->treeViewStory = 'Tree View Story';
$lang->execution->storyKanban = 'Story Kanban';
$lang->execution->storySort = 'Story sortieren';
$lang->execution->importPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
$lang->execution->importEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
$lang->execution->importBranchPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? Only the activation stories of the branch associated with this ' .$lang->executionCommon. ' will be associated with the import';
$lang->execution->importBranchEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? Only the activation stories of the branch associated with this ' .$lang->executionCommon. ' will be associated with the import';
$lang->execution->needLinkProducts = 'The execution has not been linked with any product, and the related functions cannot be used. Please link the product first and try again.';
$lang->execution->iteration = 'Iteration';
$lang->execution->iterationInfo = '%s Iterationen';
$lang->execution->viewAll = 'Alle anzeigen';
$lang->execution->testreport = 'Test Report';
$lang->execution->taskKanban = 'Task Kanban';
$lang->execution->RDKanban = 'Research & Development Kanban';
/* Group browsing. */
$lang->execution->allTasks = 'Alle';
@@ -349,7 +351,7 @@ $lang->execution->howToUpdateBurn = "<a href='https://api.zentao.pm/goto.ph
$lang->execution->whyNoStories = "Keine Story kann verknüpft werden. Bitte prüfen Sie ob ein Story mit {$lang->executionCommon} verknüpft ist {$lang->productCommon} und stellen Sie sicher das diese geprüft ist.";
$lang->execution->projectNoStories = "No story can be linked. Please check whether there is any story in project and make sure it has been reviewed.";
$lang->execution->productStories = "{$lang->executionCommon} verknüpfte Story ist ein Subset von {$lang->productCommon}, welche nur nach überprüfung verknüpft werden kann. Bitte <a href='%s'> Story verknüpfen</a>。";
$lang->execution->haveDraft = "There are %s draft stories can't be linked.";
$lang->execution->haveDraft = "There are %s draft stories or not associated with this {$lang->executionCommon} can't be linked.";
$lang->execution->doneExecutions = 'Erledigt';
$lang->execution->selectDept = 'Abteilung wählen';
$lang->execution->selectDeptTitle = 'Abteilung wählen';
+79 -77
View File
@@ -209,82 +209,84 @@ $lang->execution->burnByList['estimate'] = "View by plan hours";
$lang->execution->burnByList['storyPoint'] = 'View by story point';
/* Method list. */
$lang->execution->index = "{$lang->executionCommon} Home";
$lang->execution->task = 'Task List';
$lang->execution->groupTask = 'Group View';
$lang->execution->story = 'Story List';
$lang->execution->qa = 'QA';
$lang->execution->bug = 'Bug List';
$lang->execution->testcase = 'Testcase List';
$lang->execution->dynamic = 'Dynamics';
$lang->execution->latestDynamic = 'Dynamics';
$lang->execution->build = 'Build List';
$lang->execution->testtask = 'Request';
$lang->execution->burn = 'Burndown';
$lang->execution->computeBurn = 'Update';
$lang->execution->CFD = 'Cumulative Flow diagrams';
$lang->execution->computeCFD = 'Compute Cumulative Flow diagrams';
$lang->execution->burnData = 'Burndown Data';
$lang->execution->fixFirst = 'Edit 1st-Day Estimates';
$lang->execution->team = 'Members';
$lang->execution->doc = 'Document';
$lang->execution->doclib = 'Docoment Library';
$lang->execution->manageProducts = 'Linked ' . $lang->productCommon . 's';
$lang->execution->linkStory = 'Link Stories';
$lang->execution->linkStoryByPlan = 'Link Stories By Plan';
$lang->execution->linkPlan = 'Linked Plan';
$lang->execution->unlinkStoryTasks = 'Unlink';
$lang->execution->linkedProducts = "Linked {$lang->productCommon}s";
$lang->execution->unlinkedProducts = "Unlinked {$lang->productCommon}s";
$lang->execution->view = "Execution Detail";
$lang->execution->startAction = "Start Execution";
$lang->execution->activateAction = "Activate Execution";
$lang->execution->delayAction = "Delay Execution";
$lang->execution->suspendAction = "Suspend Execution";
$lang->execution->closeAction = "Close Execution";
$lang->execution->testtaskAction = "Execution Request";
$lang->execution->teamAction = "Execution Members";
$lang->execution->kanbanAction = "Execution Kanban";
$lang->execution->printKanbanAction = "Print Kanban";
$lang->execution->treeAction = "Execution Tree View";
$lang->execution->exportAction = "Export Execution";
$lang->execution->computeBurnAction = "Update Burndown";
$lang->execution->create = "Create {$lang->executionCommon}";
$lang->execution->createExec = "Create {$lang->execution->common}";
$lang->execution->createAction = "Create {$lang->execution->common}";
$lang->execution->copyExec = "Copy {$lang->execution->common}";
$lang->execution->copy = "Copy {$lang->executionCommon}";
$lang->execution->delete = "Delete {$lang->executionCommon}";
$lang->execution->deleteAB = "Delete Execution";
$lang->execution->browse = "{$lang->executionCommon} List";
$lang->execution->edit = "Edit {$lang->executionCommon}";
$lang->execution->editAction = "Edit Execution";
$lang->execution->batchEdit = "Edit";
$lang->execution->batchEditAction = "Batch Edit";
$lang->execution->manageMembers = 'Manage Team';
$lang->execution->unlinkMember = 'Remove Member';
$lang->execution->unlinkStory = 'Unlink Story';
$lang->execution->unlinkStoryAB = 'Unlink';
$lang->execution->batchUnlinkStory = 'Batch Unlink Stories';
$lang->execution->importTask = 'Transfer Task';
$lang->execution->importPlanStories = 'Link Stories By Plan';
$lang->execution->importBug = 'Import Bug';
$lang->execution->tree = 'Tree';
$lang->execution->treeTask = 'Show Task Only';
$lang->execution->treeStory = 'Show Story Only';
$lang->execution->treeViewTask = 'Tree View Task';
$lang->execution->treeViewStory = 'Tree View Story';
$lang->execution->storyKanban = 'Story Kanban';
$lang->execution->storySort = 'Rank Story';
$lang->execution->importPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
$lang->execution->importEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
$lang->execution->needLinkProducts = 'The execution has not been linked with any product, and the related functions cannot be used. Please link the product first and try again.';
$lang->execution->iteration = 'Iterations';
$lang->execution->iterationInfo = '%s Iterations';
$lang->execution->viewAll = 'View All';
$lang->execution->testreport = 'Test Report';
$lang->execution->taskKanban = 'Task Kanban';
$lang->execution->RDKanban = 'Research & Development Kanban';
$lang->execution->index = "{$lang->executionCommon} Home";
$lang->execution->task = 'Task List';
$lang->execution->groupTask = 'Group View';
$lang->execution->story = 'Story List';
$lang->execution->qa = 'QA';
$lang->execution->bug = 'Bug List';
$lang->execution->testcase = 'Testcase List';
$lang->execution->dynamic = 'Dynamics';
$lang->execution->latestDynamic = 'Dynamics';
$lang->execution->build = 'Build List';
$lang->execution->testtask = 'Request';
$lang->execution->burn = 'Burndown';
$lang->execution->computeBurn = 'Update';
$lang->execution->CFD = 'Cumulative Flow diagrams';
$lang->execution->computeCFD = 'Compute Cumulative Flow diagrams';
$lang->execution->burnData = 'Burndown Data';
$lang->execution->fixFirst = 'Edit 1st-Day Estimates';
$lang->execution->team = 'Members';
$lang->execution->doc = 'Document';
$lang->execution->doclib = 'Docoment Library';
$lang->execution->manageProducts = 'Linked ' . $lang->productCommon . 's';
$lang->execution->linkStory = 'Link Stories';
$lang->execution->linkStoryByPlan = 'Link Stories By Plan';
$lang->execution->linkPlan = 'Linked Plan';
$lang->execution->unlinkStoryTasks = 'Unlink';
$lang->execution->linkedProducts = "Linked {$lang->productCommon}s";
$lang->execution->unlinkedProducts = "Unlinked {$lang->productCommon}s";
$lang->execution->view = "Execution Detail";
$lang->execution->startAction = "Start Execution";
$lang->execution->activateAction = "Activate Execution";
$lang->execution->delayAction = "Delay Execution";
$lang->execution->suspendAction = "Suspend Execution";
$lang->execution->closeAction = "Close Execution";
$lang->execution->testtaskAction = "Execution Request";
$lang->execution->teamAction = "Execution Members";
$lang->execution->kanbanAction = "Execution Kanban";
$lang->execution->printKanbanAction = "Print Kanban";
$lang->execution->treeAction = "Execution Tree View";
$lang->execution->exportAction = "Export Execution";
$lang->execution->computeBurnAction = "Update Burndown";
$lang->execution->create = "Create {$lang->executionCommon}";
$lang->execution->createExec = "Create {$lang->execution->common}";
$lang->execution->createAction = "Create {$lang->execution->common}";
$lang->execution->copyExec = "Copy {$lang->execution->common}";
$lang->execution->copy = "Copy {$lang->executionCommon}";
$lang->execution->delete = "Delete {$lang->executionCommon}";
$lang->execution->deleteAB = "Delete Execution";
$lang->execution->browse = "{$lang->executionCommon} List";
$lang->execution->edit = "Edit {$lang->executionCommon}";
$lang->execution->editAction = "Edit Execution";
$lang->execution->batchEdit = "Edit";
$lang->execution->batchEditAction = "Batch Edit";
$lang->execution->manageMembers = 'Manage Team';
$lang->execution->unlinkMember = 'Remove Member';
$lang->execution->unlinkStory = 'Unlink Story';
$lang->execution->unlinkStoryAB = 'Unlink';
$lang->execution->batchUnlinkStory = 'Batch Unlink Stories';
$lang->execution->importTask = 'Transfer Task';
$lang->execution->importPlanStories = 'Link Stories By Plan';
$lang->execution->importBug = 'Import Bug';
$lang->execution->tree = 'Tree';
$lang->execution->treeTask = 'Show Task Only';
$lang->execution->treeStory = 'Show Story Only';
$lang->execution->treeViewTask = 'Tree View Task';
$lang->execution->treeViewStory = 'Tree View Story';
$lang->execution->storyKanban = 'Story Kanban';
$lang->execution->storySort = 'Rank Story';
$lang->execution->importPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
$lang->execution->importEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
$lang->execution->importBranchPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? Only the activation stories of the branch associated with this ' .$lang->executionCommon. ' will be associated with the import';
$lang->execution->importBranchEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? Only the activation stories of the branch associated with this ' .$lang->executionCommon. ' will be associated with the import';
$lang->execution->needLinkProducts = 'The execution has not been linked with any product, and the related functions cannot be used. Please link the product first and try again.';
$lang->execution->iteration = 'Iterations';
$lang->execution->iterationInfo = '%s Iterations';
$lang->execution->viewAll = 'View All';
$lang->execution->testreport = 'Test Report';
$lang->execution->taskKanban = 'Task Kanban';
$lang->execution->RDKanban = 'Research & Development Kanban';
/* Group browsing. */
$lang->execution->allTasks = 'All';
@@ -349,7 +351,7 @@ $lang->execution->howToUpdateBurn = "<a href='https://api.zentao.pm/goto.ph
$lang->execution->whyNoStories = "No story can be linked. Please check whether there is any story in {$lang->executionCommon} which is linked to {$lang->productCommon} and make sure it has been reviewed.";
$lang->execution->projectNoStories = "No story can be linked. Please check whether there is any story in project and make sure it has been reviewed.";
$lang->execution->productStories = "Stories linked to {$lang->executionCommon} are the subeset of stories linked to {$lang->productCommon}. Stories can only be linked after they pass the review. <a href='%s'> Link Stories</a> now.";
$lang->execution->haveDraft = "%s stories in draft, so they can't be linked.";
$lang->execution->haveDraft = "There are %s draft stories or not associated with this {$lang->executionCommon} can't be linked.";
$lang->execution->doneExecutions = 'Finished';
$lang->execution->selectDept = 'Select Department';
$lang->execution->selectDeptTitle = 'Select User';
+79 -77
View File
@@ -209,82 +209,84 @@ $lang->execution->burnByList['estimate'] = "View by plan hours";
$lang->execution->burnByList['storyPoint'] = 'View by story point';
/* Method list. */
$lang->execution->index = "Accueil {$lang->executionCommon}";
$lang->execution->task = 'Liste Tâches';
$lang->execution->groupTask = 'Vision Groupée';
$lang->execution->story = 'Liste Stories';
$lang->execution->qa = 'QA';
$lang->execution->bug = 'Liste Bugs';
$lang->execution->testcase = 'Testcase List';
$lang->execution->dynamic = 'Historique';
$lang->execution->latestDynamic = 'Historique';
$lang->execution->build = 'Liste Builds';
$lang->execution->testtask = 'Recette';
$lang->execution->burn = ' Atterrissage';
$lang->execution->computeBurn = 'Calculer';
$lang->execution->CFD = 'Cumulative Flow diagrams';
$lang->execution->computeCFD = 'Compute Cumulative Flow diagrams';
$lang->execution->burnData = "Données d'atterrissage";
$lang->execution->fixFirst = 'Fixer 1er-Jour Estimation';
$lang->execution->team = 'Membres';
$lang->execution->doc = 'Documents';
$lang->execution->doclib = 'Répertoire de Documents';
$lang->execution->manageProducts = 'Liaisons du ' . $lang->executionCommon . ' avec les ' . $lang->productCommon . 's';
$lang->execution->linkStory = 'Stories liées';
$lang->execution->linkStoryByPlan = 'Stories liées par Plan';
$lang->execution->linkPlan = 'Plans liés';
$lang->execution->unlinkStoryTasks = 'Dissocier';
$lang->execution->linkedProducts = "{$lang->productCommon}s liés à ce {$lang->executionCommon}";
$lang->execution->unlinkedProducts = "{$lang->productCommon}s dissociés de ce {$lang->executionCommon}";
$lang->execution->view = "Détail du Execution";
$lang->execution->startAction = "Start Execution";
$lang->execution->activateAction = "Activer le Execution";
$lang->execution->delayAction = "Ajourner le Execution";
$lang->execution->suspendAction = "Suspendre le Execution";
$lang->execution->closeAction = "Fermer le Execution";
$lang->execution->testtaskAction = "Recettes du Execution";
$lang->execution->teamAction = "Membres du Execution";
$lang->execution->kanbanAction = "Kaban Execution";
$lang->execution->printKanbanAction = "Imprimer le Kanban";
$lang->execution->treeAction = "Arborescence Execution";
$lang->execution->exportAction = "Exporter Execution";
$lang->execution->computeBurnAction = "Calculer Atterrissage";
$lang->execution->create = "Créer {$lang->executionCommon}";
$lang->execution->createExec = "Create Execution";
$lang->execution->createAction = "Create {$lang->execution->common}";
$lang->execution->copyExec = "Copy Execution";
$lang->execution->copy = "Copier {$lang->executionCommon}";
$lang->execution->delete = "Supprimer {$lang->executionCommon}";
$lang->execution->deleteAB = "Delete Execution";
$lang->execution->browse = "Liste du {$lang->executionCommon}";
$lang->execution->edit = "Editer {$lang->executionCommon}";
$lang->execution->editAction = "Edit Execution";
$lang->execution->batchEdit = "Edition par lot";
$lang->execution->batchEditAction = "Batch Edit";
$lang->execution->manageMembers = 'Organiser Equipe';
$lang->execution->unlinkMember = 'Retirer le membre';
$lang->execution->unlinkStory = 'Dissocier Story';
$lang->execution->unlinkStoryAB = 'Dissocier';
$lang->execution->batchUnlinkStory = 'Dissocier Stories par lot';
$lang->execution->importTask = 'Transfert Tâche';
$lang->execution->importPlanStories = 'Lier Stories Par Plan';
$lang->execution->importBug = 'Importer Bug';
$lang->execution->tree = 'Arboressence';
$lang->execution->treeTask = 'Seulement les Tâches';
$lang->execution->treeStory = 'Seulement les Stories';
$lang->execution->treeViewTask = 'Seulement les Tâches';
$lang->execution->treeViewStory = 'Seulement les Stories';
$lang->execution->storyKanban = 'Story Kanban';
$lang->execution->storySort = 'Rang Story';
$lang->execution->importPlanStory = $lang->executionCommon . ' est créé!\nVoulez-vous importer des stories qui ont été ajoutées au Plan ?';
$lang->execution->importEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
$lang->execution->needLinkProducts = 'The execution has not been linked with any product, and the related functions cannot be used. Please link the product first and try again.';
$lang->execution->iteration = 'Itérations';
$lang->execution->iterationInfo = '%s Itérations';
$lang->execution->viewAll = 'Voir Tout';
$lang->execution->testreport = 'Test Report';
$lang->execution->taskKanban = 'Task Kanban';
$lang->execution->RDKanban = 'Research & Development Kanban';
$lang->execution->index = "{$lang->executionCommon} Home";
$lang->execution->task = 'Task List';
$lang->execution->groupTask = 'Group View';
$lang->execution->story = 'Story List';
$lang->execution->qa = 'QA';
$lang->execution->bug = 'Bug List';
$lang->execution->testcase = 'Testcase List';
$lang->execution->dynamic = 'Dynamics';
$lang->execution->latestDynamic = 'Dynamics';
$lang->execution->build = 'Build List';
$lang->execution->testtask = 'Request';
$lang->execution->burn = 'Burndown';
$lang->execution->computeBurn = 'Update';
$lang->execution->CFD = 'Cumulative Flow diagrams';
$lang->execution->computeCFD = 'Compute Cumulative Flow diagrams';
$lang->execution->burnData = 'Burndown Data';
$lang->execution->fixFirst = 'Edit 1st-Day Estimates';
$lang->execution->team = 'Members';
$lang->execution->doc = 'Document';
$lang->execution->doclib = 'Docoment Library';
$lang->execution->manageProducts = 'Linked ' . $lang->productCommon . 's';
$lang->execution->linkStory = 'Link Stories';
$lang->execution->linkStoryByPlan = 'Link Stories By Plan';
$lang->execution->linkPlan = 'Linked Plan';
$lang->execution->unlinkStoryTasks = 'Unlink';
$lang->execution->linkedProducts = "Linked {$lang->productCommon}s";
$lang->execution->unlinkedProducts = "Unlinked {$lang->productCommon}s";
$lang->execution->view = "Execution Detail";
$lang->execution->startAction = "Start Execution";
$lang->execution->activateAction = "Activate Execution";
$lang->execution->delayAction = "Delay Execution";
$lang->execution->suspendAction = "Suspend Execution";
$lang->execution->closeAction = "Close Execution";
$lang->execution->testtaskAction = "Execution Request";
$lang->execution->teamAction = "Execution Members";
$lang->execution->kanbanAction = "Execution Kanban";
$lang->execution->printKanbanAction = "Print Kanban";
$lang->execution->treeAction = "Execution Tree View";
$lang->execution->exportAction = "Export Execution";
$lang->execution->computeBurnAction = "Update Burndown";
$lang->execution->create = "Create {$lang->executionCommon}";
$lang->execution->createExec = "Create {$lang->execution->common}";
$lang->execution->createAction = "Create {$lang->execution->common}";
$lang->execution->copyExec = "Copy {$lang->execution->common}";
$lang->execution->copy = "Copy {$lang->executionCommon}";
$lang->execution->delete = "Delete {$lang->executionCommon}";
$lang->execution->deleteAB = "Delete Execution";
$lang->execution->browse = "{$lang->executionCommon} List";
$lang->execution->edit = "Edit {$lang->executionCommon}";
$lang->execution->editAction = "Edit Execution";
$lang->execution->batchEdit = "Edit";
$lang->execution->batchEditAction = "Batch Edit";
$lang->execution->manageMembers = 'Manage Team';
$lang->execution->unlinkMember = 'Remove Member';
$lang->execution->unlinkStory = 'Unlink Story';
$lang->execution->unlinkStoryAB = 'Unlink';
$lang->execution->batchUnlinkStory = 'Batch Unlink Stories';
$lang->execution->importTask = 'Transfer Task';
$lang->execution->importPlanStories = 'Link Stories By Plan';
$lang->execution->importBug = 'Import Bug';
$lang->execution->tree = 'Tree';
$lang->execution->treeTask = 'Show Task Only';
$lang->execution->treeStory = 'Show Story Only';
$lang->execution->treeViewTask = 'Tree View Task';
$lang->execution->treeViewStory = 'Tree View Story';
$lang->execution->storyKanban = 'Story Kanban';
$lang->execution->storySort = 'Rank Story';
$lang->execution->importPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
$lang->execution->importEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
$lang->execution->importBranchPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? Only the activation stories of the branch associated with this ' .$lang->executionCommon. ' will be associated with the import';
$lang->execution->importBranchEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? Only the activation stories of the branch associated with this ' .$lang->executionCommon. ' will be associated with the import';
$lang->execution->needLinkProducts = 'The execution has not been linked with any product, and the related functions cannot be used. Please link the product first and try again.';
$lang->execution->iteration = 'Iterations';
$lang->execution->iterationInfo = '%s Iterations';
$lang->execution->viewAll = 'View All';
$lang->execution->testreport = 'Test Report';
$lang->execution->taskKanban = 'Task Kanban';
$lang->execution->RDKanban = 'Research & Development Kanban';
/* Group browsing. */
$lang->execution->allTasks = 'Toutes';
@@ -349,7 +351,7 @@ $lang->execution->howToUpdateBurn = "<a href='https://api.zentao.pm/goto.ph
$lang->execution->whyNoStories = "Aucune story ne peut être associée. Vérifiez s'il existe des stories dans {$lang->executionCommon} qui sont associées à {$lang->productCommon} et vérifiez qu'elles ont bien été validées.";
$lang->execution->projectNoStories = "No story can be linked. Please check whether there is any story in project and make sure it has been reviewed.";
$lang->execution->productStories = "Les stories associées au {$lang->executionCommon} sont une portion des stories associées au {$lang->productCommon}. Les stories ne peuvent être associées à un {$lang->executionCommon} qu'après avoir été validées. <a href='%s'> Associer Stories</a> maintenant.";
$lang->execution->haveDraft = "%s stories sont encore en conception, elles ne peuvent pas être associées au {$lang->executionCommon} actuellement.";
$lang->execution->haveDraft = "There are %s draft stories or not associated with this {$lang->executionCommon} can't be linked.";
$lang->execution->doneExecutions = 'Terminé';
$lang->execution->selectDept = 'Sélection Compartiment';
$lang->execution->selectDeptTitle = 'Sélection Utilisateur';
+79 -77
View File
@@ -209,82 +209,84 @@ $lang->execution->burnByList['estimate'] = "按计划工时查看";
$lang->execution->burnByList['storyPoint'] = '按故事点查看';
/* 方法列表。*/
$lang->execution->index = "{$lang->execution->common}主页";
$lang->execution->task = '任务列表';
$lang->execution->groupTask = '分组浏览任务';
$lang->execution->story = "{$lang->SRCommon}列表";
$lang->execution->qa = '测试仪表盘';
$lang->execution->bug = 'Bug列表';
$lang->execution->testcase = '用例列表';
$lang->execution->dynamic = '动态';
$lang->execution->latestDynamic = '最新动态';
$lang->execution->build = '所有版本';
$lang->execution->testtask = '测试单';
$lang->execution->burn = '燃尽图';
$lang->execution->computeBurn = '更新燃尽图';
$lang->execution->CFD = '累积流图';
$lang->execution->computeCFD = '更新累积流图';
$lang->execution->burnData = '燃尽图数据';
$lang->execution->fixFirst = '修改首天工时';
$lang->execution->team = '团队成员';
$lang->execution->doc = '文档列表';
$lang->execution->doclib = '文档库列表';
$lang->execution->manageProducts = '关联' . $lang->productCommon;
$lang->execution->linkStory = "关联{$lang->SRCommon}";
$lang->execution->linkStoryByPlan = "按照计划关联";
$lang->execution->linkPlan = "关联计划";
$lang->execution->unlinkStoryTasks = "未关联{$lang->SRCommon}任务";
$lang->execution->linkedProducts = '已关联';
$lang->execution->unlinkedProducts = '未关联';
$lang->execution->view = "{$lang->execution->common}概况";
$lang->execution->startAction = "开始{$lang->execution->common}";
$lang->execution->activateAction = "激活{$lang->execution->common}";
$lang->execution->delayAction = "延期{$lang->execution->common}";
$lang->execution->suspendAction = "挂起{$lang->execution->common}";
$lang->execution->closeAction = "关闭{$lang->execution->common}";
$lang->execution->testtaskAction = "{$lang->execution->common}测试单";
$lang->execution->teamAction = "{$lang->execution->common}团队";
$lang->execution->kanbanAction = "{$lang->execution->common}看板";
$lang->execution->printKanbanAction = "打印看板";
$lang->execution->treeAction = "{$lang->execution->common}树状图";
$lang->execution->exportAction = "导出{$lang->execution->common}";
$lang->execution->computeBurnAction = "计算燃尽图";
$lang->execution->create = "添加{$lang->executionCommon}";
$lang->execution->createExec = "添加{$lang->execution->common}";
$lang->execution->createAction = "添加{$lang->execution->common}";
$lang->execution->copyExec = "复制{$lang->execution->common}";
$lang->execution->copy = "复制{$lang->executionCommon}";
$lang->execution->delete = "删除{$lang->executionCommon}";
$lang->execution->deleteAB = "删除{$lang->execution->common}";
$lang->execution->browse = "浏览{$lang->execution->common}";
$lang->execution->edit = "设置{$lang->executionCommon}";
$lang->execution->editAction = "编辑{$lang->execution->common}";
$lang->execution->batchEdit = "编辑";
$lang->execution->batchEditAction = "批量编辑";
$lang->execution->manageMembers = '团队管理';
$lang->execution->unlinkMember = '移除成员';
$lang->execution->unlinkStory = "移除{$lang->SRCommon}";
$lang->execution->unlinkStoryAB = "移除{$lang->SRCommon}";
$lang->execution->batchUnlinkStory = "批量移除{$lang->SRCommon}";
$lang->execution->importTask = '转入任务';
$lang->execution->importPlanStories = "按计划关联{$lang->SRCommon}";
$lang->execution->importBug = '导入Bug';
$lang->execution->tree = '树状图';
$lang->execution->treeTask = '只看任务';
$lang->execution->treeStory = "只看{$lang->SRCommon}";
$lang->execution->treeViewTask = '树状图查看任务';
$lang->execution->treeViewStory = "树状图查看{$lang->SRCommon}";
$lang->execution->storyKanban = "{$lang->SRCommon}看板";
$lang->execution->storySort = "{$lang->SRCommon}排序";
$lang->execution->importPlanStory = '创建' . $lang->executionCommon . '成功!\n是否导入计划关联的相关' . $lang->SRCommon . '?导入时将自动过滤掉草稿状态的' . $lang->SRCommon . '。';
$lang->execution->importEditPlanStory = '编辑' . $lang->executionCommon . '成功!\n是否导入计划关联的相关' . $lang->SRCommon . '?导入时将自动过滤掉草稿状态的' . $lang->SRCommon . '。';
$lang->execution->needLinkProducts = '该执行还未关联任何产品,相关功能无法使用,请先关联产品后再试。';
$lang->execution->iteration = '版本迭代';
$lang->execution->iterationInfo = '迭代%s次';
$lang->execution->viewAll = '查看所有';
$lang->execution->testreport = '测试报告';
$lang->execution->taskKanban = '任务看板';
$lang->execution->RDKanban = '研发看板';
$lang->execution->index = "{$lang->execution->common}主页";
$lang->execution->task = '任务列表';
$lang->execution->groupTask = '分组浏览任务';
$lang->execution->story = "{$lang->SRCommon}列表";
$lang->execution->qa = '测试仪表盘';
$lang->execution->bug = 'Bug列表';
$lang->execution->testcase = '用例列表';
$lang->execution->dynamic = '动态';
$lang->execution->latestDynamic = '最新动态';
$lang->execution->build = '所有版本';
$lang->execution->testtask = '测试单';
$lang->execution->burn = '燃尽图';
$lang->execution->computeBurn = '更新燃尽图';
$lang->execution->CFD = '累积流图';
$lang->execution->computeCFD = '更新累积流图';
$lang->execution->burnData = '燃尽图数据';
$lang->execution->fixFirst = '修改首天工时';
$lang->execution->team = '团队成员';
$lang->execution->doc = '文档列表';
$lang->execution->doclib = '文档库列表';
$lang->execution->manageProducts = '关联' . $lang->productCommon;
$lang->execution->linkStory = "关联{$lang->SRCommon}";
$lang->execution->linkStoryByPlan = "按照计划关联";
$lang->execution->linkPlan = "关联计划";
$lang->execution->unlinkStoryTasks = "未关联{$lang->SRCommon}任务";
$lang->execution->linkedProducts = '已关联';
$lang->execution->unlinkedProducts = '未关联';
$lang->execution->view = "{$lang->execution->common}概况";
$lang->execution->startAction = "开始{$lang->execution->common}";
$lang->execution->activateAction = "激活{$lang->execution->common}";
$lang->execution->delayAction = "延期{$lang->execution->common}";
$lang->execution->suspendAction = "挂起{$lang->execution->common}";
$lang->execution->closeAction = "关闭{$lang->execution->common}";
$lang->execution->testtaskAction = "{$lang->execution->common}测试单";
$lang->execution->teamAction = "{$lang->execution->common}团队";
$lang->execution->kanbanAction = "{$lang->execution->common}看板";
$lang->execution->printKanbanAction = "打印看板";
$lang->execution->treeAction = "{$lang->execution->common}树状图";
$lang->execution->exportAction = "导出{$lang->execution->common}";
$lang->execution->computeBurnAction = "计算燃尽图";
$lang->execution->create = "添加{$lang->executionCommon}";
$lang->execution->createExec = "添加{$lang->execution->common}";
$lang->execution->createAction = "添加{$lang->execution->common}";
$lang->execution->copyExec = "复制{$lang->execution->common}";
$lang->execution->copy = "复制{$lang->executionCommon}";
$lang->execution->delete = "删除{$lang->executionCommon}";
$lang->execution->deleteAB = "删除{$lang->execution->common}";
$lang->execution->browse = "浏览{$lang->execution->common}";
$lang->execution->edit = "设置{$lang->executionCommon}";
$lang->execution->editAction = "编辑{$lang->execution->common}";
$lang->execution->batchEdit = "编辑";
$lang->execution->batchEditAction = "批量编辑";
$lang->execution->manageMembers = '团队管理';
$lang->execution->unlinkMember = '移除成员';
$lang->execution->unlinkStory = "移除{$lang->SRCommon}";
$lang->execution->unlinkStoryAB = "移除{$lang->SRCommon}";
$lang->execution->batchUnlinkStory = "批量移除{$lang->SRCommon}";
$lang->execution->importTask = '转入任务';
$lang->execution->importPlanStories = "按计划关联{$lang->SRCommon}";
$lang->execution->importBug = '导入Bug';
$lang->execution->tree = '树状图';
$lang->execution->treeTask = '只看任务';
$lang->execution->treeStory = "只看{$lang->SRCommon}";
$lang->execution->treeViewTask = '树状图查看任务';
$lang->execution->treeViewStory = "树状图查看{$lang->SRCommon}";
$lang->execution->storyKanban = "{$lang->SRCommon}看板";
$lang->execution->storySort = "{$lang->SRCommon}排序";
$lang->execution->importPlanStory = '创建' . $lang->executionCommon . '成功!\n是否导入计划关联的相关' . $lang->SRCommon . '?导入时将自动过滤掉草稿状态的' . $lang->SRCommon . '。';
$lang->execution->importEditPlanStory = '编辑' . $lang->executionCommon . '成功!\n是否导入计划关联的相关' . $lang->SRCommon . '?导入时将自动过滤掉草稿状态的' . $lang->SRCommon . '。';
$lang->execution->importBranchPlanStory = '创建' . $lang->executionCommon . '成功!\n是否导入计划关联的相关' . $lang->SRCommon . '?导入时将只关联本' . $lang->executionCommon . '所关联分支的激活需求。';
$lang->execution->importBranchEditPlanStory = '编辑' . $lang->executionCommon . '成功!\n是否导入计划关联的相关' . $lang->SRCommon . '?导入时将只关联本' . $lang->executionCommon . '所关联分支的激活需求。';
$lang->execution->needLinkProducts = '该执行还未关联任何产品,相关功能无法使用,请先关联产品后再试。';
$lang->execution->iteration = '版本迭代';
$lang->execution->iterationInfo = '迭代%s次';
$lang->execution->viewAll = '查看所有';
$lang->execution->testreport = '测试报告';
$lang->execution->taskKanban = '任务看板';
$lang->execution->RDKanban = '研发看板';
/* 分组浏览。*/
$lang->execution->allTasks = '所有';
@@ -349,7 +351,7 @@ $lang->execution->howToUpdateBurn = "<a href='https://api.zentao.net/goto.p
$lang->execution->whyNoStories = "看起来没有{$lang->SRCommon}可以关联。请检查下{$lang->executionCommon}关联的{$lang->productCommon}中有没有{$lang->SRCommon},而且要确保它们已经审核通过。";
$lang->execution->projectNoStories = "看起来没有{$lang->SRCommon}可以关联。请检查下项目中有没有{$lang->SRCommon},而且要确保它们已经审核通过。";
$lang->execution->productStories = "{$lang->executionCommon}关联的{$lang->SRCommon}是{$lang->productCommon}{$lang->SRCommon}的子集,并且只有评审通过的{$lang->SRCommon}才能关联。请<a href='%s'>关联{$lang->SRCommon}</a>。";
$lang->execution->haveDraft = "有%s条草稿状态的{$lang->SRCommon}无法关联到该{$lang->executionCommon}";
$lang->execution->haveDraft = "有%s条草稿状态或非本{$lang->executionCommon}关联分支的{$lang->SRCommon}无法关联到该{$lang->executionCommon}";
$lang->execution->doneExecutions = '已结束';
$lang->execution->selectDept = '选择部门';
$lang->execution->selectDeptTitle = '选择一个部门的成员';
+79 -24
View File
@@ -332,6 +332,20 @@ class executionModel extends model
$this->checkBeginAndEndDate($_POST['project'], $_POST['begin'], $_POST['end']);
if(dao::isError()) return false;
if($_POST['products'])
{
$this->app->loadLang('project');
$multipleProducts = $this->loadModel('product')->getMultiBranchPairs();
foreach($_POST['products'] as $index => $productID)
{
if(isset($multipleProducts[$productID]) and empty($_POST['branch'][$index]))
{
dao::$errors[] = $this->lang->project->emptyBranch;
return false;
}
}
}
/* Determine whether to add a sprint or a stage according to the model of the execution. */
$project = $this->loadModel('project')->getByID($_POST['project']);
$type = 'sprint';
@@ -514,6 +528,20 @@ class executionModel extends model
return false;
}
if($_POST['products'])
{
$this->app->loadLang('project');
$multipleProducts = $this->loadModel('product')->getMultiBranchPairs();
foreach($_POST['products'] as $index => $productID)
{
if(isset($multipleProducts[$productID]) and empty($_POST['branch'][$index]))
{
dao::$errors[] = $this->lang->project->emptyBranch;
return false;
}
}
}
/* Get the data from the post. */
$execution = fixer::input('post')
->add('id', $executionID)
@@ -2370,23 +2398,28 @@ class executionModel extends model
$oldPlan = 0;
$branch = isset($branches[$i]) ? $branches[$i] : 0;
if(isset($existedProducts[$productID][$branch])) continue;
if(!is_array($branch)) $branch = array($branch);
if(isset($oldProducts[$productID][$branch]))
foreach($branch as $branchID)
{
$oldProduct = $oldProducts[$productID][$branch];
if($this->app->rawMethod != 'edit') $oldPlan = $oldProduct->plan;
}
if(isset($existedProducts[$productID][$branchID])) continue;
$data = new stdclass();
$data->project = $executionID;
$data->product = $productID;
$data->branch = $branch;
$data->plan = isset($plans[$productID][$branch]) ? implode(',', $plans[$productID][$branch]) : $oldPlan;
$data->plan = trim($data->plan, ',');
$data->plan = empty($data->plan) ? 0 : ",$data->plan,";
$this->dao->insert(TABLE_PROJECTPRODUCT)->data($data)->exec();
$existedProducts[$productID][$branch] = true;
if(isset($oldProducts[$productID][$branchID]))
{
$oldProduct = $oldProducts[$productID][$branchID];
if($this->app->rawMethod != 'edit') $oldPlan = $oldProduct->plan;
}
$data = new stdclass();
$data->project = $executionID;
$data->product = $productID;
$data->branch = $branchID;
$data->plan = isset($plans[$productID]) ? implode(',', $plans[$productID]) : $oldPlan;
$data->plan = trim($data->plan, ',');
$data->plan = empty($data->plan) ? 0 : ",$data->plan,";
$this->dao->insert(TABLE_PROJECTPRODUCT)->data($data)->exec();
$existedProducts[$productID][$branchID] = true;
}
}
$oldProductKeys = array_keys($oldProducts);
@@ -2882,19 +2915,21 @@ class executionModel extends model
*/
public function linkStories($executionID)
{
$plans = $this->dao->select('plan')->from(TABLE_PROJECTPRODUCT)
$plans = $this->dao->select('product, plan')->from(TABLE_PROJECTPRODUCT)
->where('project')->eq($executionID)
->fetchPairs('plan');
->fetchPairs('product', 'plan');
$planStories = array();
$planProducts = array();
$this->loadModel('story');
if(!empty($plans))
{
foreach($plans as $planIdList)
$executionProducts = $this->loadModel('project')->getBranchesByProject($executionID);
foreach($plans as $productID => $planIdList)
{
if(empty($planIdList)) continue;
$planIdList = explode(',', $planIdList);
$executionBranches = zget($executionProducts, $productID, array());
foreach($planIdList as $planID)
{
$planStory = $this->story->getPlanStories($planID);
@@ -2902,7 +2937,7 @@ class executionModel extends model
{
foreach($planStory as $id => $story)
{
if($story->status == 'draft' or $story->status == 'reviewing')
if($story->status == 'draft' or $story->status == 'reviewing' or (!empty($story->branch) and !empty($executionBranches) and !isset($executionBranches[$story->branch])))
{
unset($planStory[$id]);
continue;
@@ -4623,17 +4658,36 @@ class executionModel extends model
$branchGroups = $this->getBranchByProduct(array_keys($products), $executionID, 'noclosed');
foreach($branchGroups as $branches)
{
foreach($branches as $branchID => $branchName) $branchIdList[$branchID] = $branchID;
foreach($branches as $branchID => $branchName) $branchIdList[] = $branchID;
}
$plans = $this->dao->select('id,title,product,parent,begin,end')->from(TABLE_PRODUCTPLAN)
->where('product')->in(array_keys($products))
->andWhere('deleted')->eq(0)
->andWhere('branch')->in($branchIdList)->fi()
->orderBy('begin desc')
$branchQuery = '(';
if(!empty($branchIdList))
{
$branchCount = count($branchIdList);
foreach($branchIdList as $index => $branchID)
{
$branchQuery .= "FIND_IN_SET('$branchID', branch)";
if($index < $branchCount - 1) $branchQuery .= ' OR ';
}
}
else
{
$branchQuery .= "FIND_IN_SET('0', branch)";
}
$branchQuery .= ')';
$plans = $this->dao->select('t1.id,t1.title,t1.product,t1.parent,t1.begin,t1.end,t1.branch,t2.type as productType')->from(TABLE_PRODUCTPLAN)->alias('t1')
->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t2.id=t1.product')
->where('t1.product')->in(array_keys($products))
->andWhere('t1.deleted')->eq(0)
->andWhere($branchQuery)
->orderBy('t1.begin desc')
->fetchAll('id');
$plans = $this->productplan->reorder4Children($plans);
$plans = $this->productplan->relationBranch($plans);
$productPlans = array();
foreach($plans as $plan)
{
@@ -4641,6 +4695,7 @@ class executionModel extends model
if($plan->parent > 0 and isset($plans[$plan->parent])) $plan->title = $plans[$plan->parent]->title . ' /' . $plan->title;
$productPlans[$plan->product][$plan->id] = $plan->title . " [{$plan->begin} ~ {$plan->end}]";
if($plan->begin == '2030-01-01' and $plan->end == '2030-01-01') $productPlans[$plan->product][$plan->id] = $plan->title . ' ' . $this->lang->productplan->future;
if($plan->productType != 'normal') $productPlans[$plan->product][$plan->id] = $productPlans[$plan->product][$plan->id] . ' / ' . ($plan->branchName ? $plan->branchName : $this->lang->branch->main);
}
return $productPlans;
+62 -43
View File
@@ -130,62 +130,81 @@
<?php $this->printExtendFields('', 'table', 'columns=3');?>
<?php $hidden = 'hide'?>
<?php if(!empty($project->hasProduct)) $hidden = ''?>
<?php if($products):?>
<?php $i = 0;?>
<?php foreach($products as $product):?>
<tr class="<?php echo $hidden;?>">
<th><?php echo $lang->execution->manageProducts;?></th>
<td class='text-left' id='productsBox' colspan="3">
<th><?php echo $lang->project->manageProductPlan;?></th>
<td class='text-left productsBox' colspan="3">
<div class='row'>
<?php $i = 0;?>
<?php $class = $division ? '' : 'disabled';?>
<?php foreach($products as $product):?>
<?php $hasBranch = ($product->type != 'normal' and isset($branchGroups[$product->id]));?>
<?php foreach($linkedBranches[$product->id] as $branchID => $branch):?>
<div class='col-sm-4'>
<div class="input-group<?php if($hasBranch) echo ' has-branch';?>">
<?php echo html::select("products[$i]", $allProducts, $product->id, "class='form-control chosen' $class onchange='loadBranches(this)' data-last='" . $product->id . "'");?>
<?php if($class) echo html::hidden("products[$i]", $product->id);?>
<span class='input-group-addon fix-border'></span>
<?php if($hasBranch) echo html::select("branch[$i]", $branchGroups[$product->id], $branchID, "class='form-control chosen' onchange=\"loadPlans('#products{$i}', this.value)\"");?>
<div class="col-sm-6">
<div class='table-row'>
<div class='table-col'>
<?php $hasBranch = $product->type != 'normal' and isset($branchGroups[$product->id]);?>
<div class='input-group <?php if($hasBranch) echo ' has-branch';?>'>
<span class='input-group-addon'><?php echo $lang->product->common;?></span>
<?php echo html::select("products[$i]", $allProducts, $product->id, "class='form-control chosen' onchange='loadBranches(this)' data-last='" . $product->id . "' data-type='" . $product->type . "'");?>
</div>
</div>
<div class='table-col <?php if(!$hasBranch) echo 'hidden';?>'>
<div class='input-group required'>
<span class='input-group-addon fix-border'><?php echo $lang->product->branchName['branch'];?></span>
<?php $branchIdList = join(',', $product->branches);?>
<?php echo html::select("branch[$i][]", isset($branchGroups[$product->id]) ? $branchGroups[$product->id] : array(), $branchIdList, "class='form-control chosen' multiple onchange=\"loadPlans('#products{$i}', this)\"");?>
</div>
</div>
</div>
</div>
<?php $i++;?>
<?php endforeach;?>
<?php endforeach;?>
<?php if((isset($project->model) and $project->model == 'scrum') or empty($products)):?>
<div class='col-sm-4'>
<div class="input-group">
<?php echo html::select("products[$i]", $allProducts, '', "class='form-control chosen' onchange='loadBranches(this)'");?>
<span class='input-group-addon fix-border'></span>
<div class="col-sm-6">
<div class='input-group' <?php echo "id='plan$i'";?>>
<span class='input-group-addon'><?php echo $lang->product->plan;?></span>
<?php echo html::select("plans[$product->id][]", isset($productPlans[$product->id]) ? $productPlans[$product->id] : array(), $product->plans, "class='form-control chosen' multiple");?>
<div class='input-group-btn'>
<a href='javascript:;' onclick='addNewLine(this)' class='btn btn-link addLine'><i class='icon-plus'></i></a>
<a href='javascript:;' onclick='removeLine(this)' class='btn btn-link removeLine' <?php if($i == 0) echo "style='visibility: hidden'";?>><i class='icon-close'></i></a>
</div>
</div>
</div>
<?php endif;?>
</div>
</td>
</tr>
<?php $hidden = 'hide'?>
<?php if(isset($project->model) and !empty($project->hasProduct) and $project->model == 'scrum') $hidden = '';?>
<tr class="<?php echo $hidden?>">
<th><?php echo $lang->execution->linkPlan;?></th>
<td colspan="3" id="plansBox">
<?php $i ++;?>
<?php endforeach;?>
<?php else:?>
<tr class='<?php echo $hidden;?>'>
<th id='productTitle'><?php echo $lang->project->manageProductPlan;?></th>
<td class='text-left productsBox' colspan='3'>
<div class='row'>
<?php if(isset($plan) && !empty($plan->begin)):?>
<div class="col-sm-4" id="plan0"><?php echo html::select("plans[{$plan->product}][{$plan->branch}][]", $productPlan, $plan->id, "class='form-control chosen' multiple");?></div>
<?php js::set('currentPlanID', $plan->id)?>
<?php elseif($copyExecutionID):?>
<?php $i = 0;?>
<?php foreach($products as $product):?>
<?php foreach($linkedBranches[$product->id] as $branchID => $branch):?>
<?php $plans = isset($productPlans[$product->id][$branchID]) ? $productPlans[$product->id][$branchID] : array();?>
<div class="col-sm-4" id="plan<?php echo $i;?>"><?php echo html::select("plans[{$product->id}][$branchID][]", $plans, $branches[$product->id][$branchID]->plan, "class='form-control chosen' multiple");?></div>
<?php $i++;?>
<?php endforeach;?>
<?php endforeach;?>
<?php else:?>
<div class="col-sm-4" id="plan0"><?php echo html::select("plans[][][]", $productPlan, '', "class='form-control chosen' multiple");?></div>
<?php js::set('currentPlanID', '')?>
<?php endif;?>
<div class="col-sm-6">
<div class='table-row'>
<div class='table-col'>
<div class='input-group'>
<span class='input-group-addon'><?php echo $lang->product->common;?></span>
<?php echo html::select("products[0]", $allProducts, '', "class='form-control chosen' onchange='loadBranches(this)'");?>
</div>
</div>
<div class='table-col hidden'>
<div class='input-group required'>
<span class='input-group-addon fix-border'><?php echo $lang->product->branchName['branch'];?></span>
<?php echo html::select("branch", '', '', "class='form-control chosen' multiple");?>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class='input-group' id='plan0'>
<span class='input-group-addon'><?php echo $lang->product->plan;?></span>
<?php echo html::select("plans[][]", $productPlan, '', "class='form-control chosen' multiple");?>
<div class='input-group-btn'>
<a href='javascript:;' onclick='addNewLine(this)' class='btn btn-link addLine'><i class='icon-plus'></i></a>
<a href='javascript:;' onclick='removeLine(this)' class='btn btn-link removeLine' style='visibility: hidden'><i class='icon-close'></i></a>
</div>
</div>
</div>
</div>
</td>
</tr>
<?php endif;?>
<tr>
<th><?php echo $lang->execution->teamname;?></th>
<td><?php echo html::input('team', $team, "class='form-control'");?></td>
+63 -37
View File
@@ -141,56 +141,81 @@
<?php if(!in_array($execution->attribute, array('request', 'design', 'review'))): ?>
<?php $hidden = 'hide'?>
<?php if(!empty($project->hasProduct)) $hidden = ''?>
<?php if($linkedProducts):?>
<?php $i = 0;?>
<?php foreach($linkedProducts as $product):?>
<tr class="<?php echo $hidden;?>">
<th><?php echo $lang->execution->manageProducts;?></th>
<td class='text-left' id='productsBox' colspan="2">
<?php $class = ($execution->grade == 2 or $execution->type == 'stage') ? "disabled" : '';?>
<th><?php if($i == 0) echo $lang->project->manageProductPlan;?></th>
<td class='text-left productsBox' colspan="3">
<div class='row'>
<?php $i = 0;?>
<?php foreach($linkedProducts as $product):?>
<?php $hasBranch = $product->type != 'normal' and isset($branchGroups[$product->id]);?>
<?php foreach($linkedBranches[$product->id] as $branchID => $branch):?>
<div class='col-sm-4'>
<div class="input-group<?php if($hasBranch) echo ' has-branch';?>">
<?php echo html::select("products[$i]", $allProducts, $product->id, "class='form-control chosen' $class onchange='loadBranches(this)' data-last='" . $product->id . "' data-type='". $product->type ."' data-lastBranch='" . $branchID . "'");?>
<span class='input-group-addon fix-border'></span>
<?php if($hasBranch) echo html::select("branch[$i]", $branchGroups[$product->id], $branchID, "class='form-control chosen' $class onchange=\"loadPlans('#products{$i}', this.value)\" data-last='" . $branchID . "'");?>
<div class="col-sm-6">
<div class='table-row'>
<div class='table-col'>
<?php $hasBranch = $product->type != 'normal' and isset($branchGroups[$product->id]);?>
<div class='input-group <?php if($hasBranch) echo ' has-branch';?>'>
<span class='input-group-addon'><?php echo $lang->product->common;?></span>
<?php echo html::select("products[$i]", $allProducts, $product->id, "class='form-control chosen' onchange='loadBranches(this)' data-last='" . $product->id . "' data-type='" . $product->type . "'");?>
</div>
</div>
<div class='table-col <?php if(!$hasBranch) echo 'hidden';?>'>
<div class='input-group required'>
<span class='input-group-addon fix-border'><?php echo $lang->product->branchName['branch'];?></span>
<?php $branchIdList = join(',', $product->branches);?>
<?php echo html::select("branch[$i][]", isset($branchGroups[$product->id]) ? $branchGroups[$product->id] : array(), $branchIdList, "class='form-control chosen' multiple onchange=\"loadPlans('#products{$i}', this)\"");?>
</div>
</div>
</div>
</div>
<?php $i++; ?>
<?php endforeach;?>
<?php endforeach;?>
<?php if($execution->type != 'stage'):?>
<div class='col-sm-4'>
<div class="input-group">
<?php echo html::select("products[$i]", $allProducts, '', "class='form-control chosen' onchange='loadBranches(this)'");?>
<span class='input-group-addon fix-border'></span>
<div class="col-sm-6">
<div class='input-group' <?php echo "id='plan$i'";?>>
<span class='input-group-addon'><?php echo $lang->product->plan;?></span>
<?php echo html::select("plans[$product->id][]", isset($productPlans[$product->id]) ? $productPlans[$product->id] : array(), $product->plans, "class='form-control chosen' multiple");?>
<div class='input-group-btn'>
<a href='javascript:;' onclick='addNewLine(this)' class='btn btn-link addLine'><i class='icon-plus'></i></a>
<a href='javascript:;' onclick='removeLine(this)' class='btn btn-link removeLine' <?php if($i == 0) echo "style='visibility: hidden'";?>><i class='icon-close'></i></a>
</div>
</div>
</div>
<?php endif;?>
</div>
</td>
</tr>
<?php if(isset($project->model) and $project->model == 'scrum') $hidden = '';?>
<tr class="<?php echo $hidden?>">
<th><?php echo $lang->execution->linkPlan;?></th>
<td id="plansBox" colspan="2">
<?php $i ++;?>
<?php endforeach;?>
<?php else:?>
<tr class='<?php echo $hidden;?>'>
<th id='productTitle'><?php echo $lang->project->manageProductPlan;?></th>
<td class='text-left productsBox' colspan='3'>
<div class='row'>
<?php $i = 0;?>
<?php if(empty($linkedProducts)):?>
<div class="col-sm-4" id="plan0"><?php echo html::select("plans[][][]", $productPlans, '', "class='form-control chosen' multiple");?></div>
<?php else:?>
<?php foreach($linkedProducts as $product):?>
<?php foreach($linkedBranches[$product->id] as $branchID => $branch):?>
<?php $plans = isset($productPlans[$product->id][$branchID]) ? $productPlans[$product->id][$branchID] : array();?>
<div class="col-sm-4" id="plan<?php echo $i;?>"><?php echo html::select("plans[{$product->id}][{$branchID}][]", $plans, $branches[$product->id][$branchID]->plan, "class='form-control chosen' multiple");?></div>
<?php $i++;?>
<?php endforeach;?>
<?php endforeach;?>
<?php endif;?>
<div class="col-sm-6">
<div class='table-row'>
<div class='table-col'>
<div class='input-group'>
<span class='input-group-addon'><?php echo $lang->product->common;?></span>
<?php echo html::select("products[0]", $allProducts, '', "class='form-control chosen' onchange='loadBranches(this)'");?>
</div>
</div>
<div class='table-col hidden'>
<div class='input-group required'>
<span class='input-group-addon fix-border'><?php echo $lang->product->branchName['branch'];?></span>
<?php echo html::select("branch", '', '', "class='form-control chosen' multiple");?>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class='input-group' id='plan0'>
<span class='input-group-addon'><?php echo $lang->product->plan;?></span>
<?php echo html::select("plans[][]", '', '', "class='form-control chosen' multiple");?>
<div class='input-group-btn'>
<a href='javascript:;' onclick='addNewLine(this)' class='btn btn-link addLine'><i class='icon-plus'></i></a>
<a href='javascript:;' onclick='removeLine(this)' class='btn btn-link removeLine' style='visibility: hidden'><i class='icon-close'></i></a>
</div>
</div>
</div>
</div>
</td>
</tr>
<?php endif; ?>
<?php else: ?>
<?php echo html::hidden("products[]", key($linkedProducts));?>
<?php endif; ?>
@@ -232,5 +257,6 @@
<?php js::set('confirmSync', $lang->execution->confirmSync);?>
<?php js::set('allProducts', $allProducts);?>
<?php js::set('branchGroups', $branchGroups);?>
<?php js::set('projectID', $execution->project);?>
<?php js::set('unLinkProductTip', $lang->project->unLinkProductTip);?>
<?php include '../../common/view/footer.html.php';?>
+2
View File
@@ -546,6 +546,7 @@ $lang->resource->story->batchChangeModule = 'batchChangeModule';
$lang->resource->story->batchToTask = 'batchToTask';
$lang->resource->story->processStoryChange = 'processStoryChange';
$lang->resource->story->linkStories = 'linkStoriesAB';
$lang->resource->story->relieved = 'relievedSiblings';
$lang->story->methodOrder[5] = 'create';
$lang->story->methodOrder[10] = 'batchCreate';
@@ -575,6 +576,7 @@ $lang->story->methodOrder[120] = 'batchChangeModule';
$lang->story->methodOrder[125] = 'batchToTask';
$lang->story->methodOrder[130] = 'processStoryChange';
$lang->story->methodOrder[135] = 'linkStories';
$lang->story->methodOrder[140] = 'relieved';
/* Requirement. */
$lang->resource->requirement = new stdclass();
+5 -2
View File
@@ -818,8 +818,11 @@ class kanbanModel extends model
}
elseif($branchID)
{
$branchName = $this->branch->getById($branchID);
$branches = array($branchID => $branchName);
foreach(explode(',', $branchID) as $id)
{
$branchName = $this->branch->getById($id);
$branches[$id] = $branchName;
}
}
foreach($branches as $id => $name)
+27 -4
View File
@@ -257,7 +257,7 @@ class product extends control
$this->products = $this->product->getProducts($projectID, 'all', '', false);
$projectProducts = $this->product->getProducts($projectID);
$productPlans = $this->execution->getPlans($projectProducts, 'skipParent');
$productPlans = $this->execution->getPlans($projectProducts, 'skipParent', $projectID);
if($browseType == 'bybranch') $param = $branchID;
$stories = $this->story->getExecutionStories($projectID, $productID, $branchID, $sort, $browseType, $param, $storyType, '', $pager);
@@ -923,6 +923,22 @@ class product extends control
}
}
/**
* Ajax get product by id.
*
* @param int $productID
* @access public
* @return void
*/
public function ajaxGetProductById($productID)
{
$product = $this->product->getById($productID);
$product->branchSourceName = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
$product->branchName = $this->lang->product->branchName[$product->type];
echo json_encode($product);
}
/**
* AJAX: get projects of a product in html select.
*
@@ -1037,12 +1053,19 @@ class product extends control
public function ajaxGetPlans($productID, $branch = 0, $planID = 0, $fieldID = '', $needCreate = false, $expired = '', $param = '')
{
$param = strtolower($param);
$plans = $this->loadModel('productplan')->getPairs($productID, $branch, $expired, strpos($param, 'skipparent') !== false);
$field = $fieldID ? "plans[$fieldID]" : 'plan';
if(strpos($param, 'forstory') === false)
{
$plans = $this->loadModel('productplan')->getPairs($productID, $branch, $expired, strpos($param, 'skipparent') !== false);
}
else
{
$plans = $this->loadModel('productplan')->getPairsForStory($productID, $branch == '0' ? 'all' : $branch, $param);
}
$field = $fieldID !== '' ? "plans[$fieldID]" : 'plan';
$multiple = strpos($param, 'multiple') === false ? '' : 'multiple';
$output = html::select($field, $plans, $planID, "class='form-control chosen' $multiple");
if($branch == 0 and strpos($param, 'edit')) $output = html::select($field, $plans, $planID, "class='form-control chosen' multiple");
if($branch == 0 and strpos($param, 'edit') and (strpos($param, 'forstory') === false)) $output = html::select($field, $plans, $planID, "class='form-control chosen' multiple");
if(count($plans) == 1 and $needCreate and $needCreate !== 'false')
{
+113 -32
View File
@@ -84,7 +84,7 @@ class productplan extends control
}
$this->commonAction($productID, $branchID);
$lastPlan = $this->productplan->getLast($productID, $branchID, $parent);
$lastPlan = $this->productplan->getLast($productID, '', $parent);
$product = $this->loadModel('product')->getById($productID);
if($lastPlan)
@@ -117,7 +117,7 @@ class productplan extends control
$this->view->branches = $branchPairs;
$this->view->defaultBranch = $defaultBranch;
$this->view->parent = $parent;
$this->view->parentPlanPairs = $this->productplan->getTopPlanPairs($productID, $branchID, 'done,closed');
$this->view->parentPlanPairs = $this->productplan->getTopPlanPairs($productID, '', 'done,closed');
$this->display();
}
@@ -136,7 +136,7 @@ class productplan extends control
$changes = $this->productplan->update($planID);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$change[$planID] = $changes;
$this->syncStory($change);
$this->unlinkOldBranch($change);
if($changes)
{
$actionID = $this->loadModel('action')->create('productplan', $planID, 'edited');
@@ -178,7 +178,7 @@ class productplan extends control
if(!empty($_POST['title']))
{
$changes = $this->productplan->batchUpdate($productID);
$this->syncStory($changes);
$this->unlinkOldBranch($changes);
$this->loadModel('action');
foreach($changes as $planID => $change)
{
@@ -194,15 +194,21 @@ class productplan extends control
$this->commonAction($productID, $branch);
$plans = $this->productplan->getByIDList($this->post->planIDList);
$oldBranch = array();
$plans = $this->productplan->getByIDList($this->post->planIDList);
$oldBranch = array();
$parentIdList = array();
foreach($plans as $plan) $oldBranch[$plan->id] = $plan->branch;
foreach($plans as $plan)
{
$oldBranch[$plan->id] = $plan->branch;
$parentIdList[$plan->parent] = $plan->parent;
}
$this->view->title = $this->lang->productplan->batchEdit;
$this->view->plans = $plans;
$this->view->oldBranch = $oldBranch;
$this->view->product = $this->loadModel('product')->getById($productID);;
$this->view->title = $this->lang->productplan->batchEdit;
$this->view->plans = $plans;
$this->view->oldBranch = $oldBranch;
$this->view->product = $this->loadModel('product')->getById($productID);;
$this->view->parentList = $this->productplan->getByIDList($parentIdList);
$this->display();
}
@@ -447,8 +453,6 @@ class productplan extends control
if($plan->parent > 0) $this->view->parentPlan = $this->productplan->getById($plan->parent);
if($plan->parent == '-1') $this->view->childrenPlans = $this->productplan->getChildren($plan->id);
if($plan->branch > 0) $this->view->branchStatus = $this->loadModel('branch')->getById($plan->branch, $plan->product, 'status');
$storyIdList = array();
$modulePairs = $this->loadModel('tree')->getOptionMenu($plan->product, 'story', 0, 'all');
foreach($planStories as $story)
@@ -675,7 +679,7 @@ class productplan extends control
$this->config->product->search['style'] = 'simple';
$this->config->product->search['params']['product']['values'] = $products + array('all' => $this->lang->product->allProductsOfProject);
$this->config->product->search['params']['plan']['values'] = $this->productplan->getPairsForStory($plan->product, $plan->branch, 'skipParent|withMainPlan');
$this->config->product->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($plan->product, 'story', 0, $plan->branch);
$this->config->product->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($plan->product, 'story', 0, 'all');
$storyStatusList = $this->lang->story->statusList;
unset($storyStatusList['closed']);
$this->config->product->search['params']['status'] = array('operator' => '=', 'control' => 'select', 'values' => $storyStatusList);
@@ -687,8 +691,9 @@ class productplan extends control
else
{
$this->config->product->search['fields']['branch'] = $this->lang->product->branch;
$branchName = $this->loadModel('branch')->getById($plan->branch);
$branches = array('' => '', BRANCH_MAIN => $this->lang->branch->main, $plan->branch => $branchName);
$branchPairs = $this->dao->select('id, name')->from(TABLE_BRANCH)->where('id')->in($plan->branch)->fetchPairs();
$branches = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branchPairs;
$this->config->product->search['params']['branch']['values'] = $branches;
}
$this->loadModel('search')->setSearchParams($this->config->product->search);
@@ -809,9 +814,12 @@ class productplan extends control
$this->config->bug->search['style'] = 'simple';
$this->config->bug->search['params']['plan']['values'] = $this->productplan->getPairsForStory($productID, $plan->branch, 'skipParent|withMainPlan');
$this->config->bug->search['params']['execution']['values'] = $this->loadModel('product')->getExecutionPairsByProduct($plan->product, $plan->branch);
$this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs($productID, $branch = 'all', $params = '');
$this->config->bug->search['params']['resolvedBuild']['values'] = $this->build->getBuildPairs($productID, $branch = 'all', $params = '');
$this->config->bug->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($plan->product, 'bug', 0, 'all');
$this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs($productID, $branch = 'all', $params = 'releasetag');
$this->config->bug->search['params']['resolvedBuild']['values'] = $this->config->bug->search['params']['openedBuild']['values'];
$this->config->bug->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($plan->product, 'bug', 0, $plan->branch);
$this->config->bug->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($plan->product, 'bug', 0, 'all');
$this->config->bug->search['params']['project']['values'] = $this->product->getProjectPairsByProduct($productID, $plan->branch);
unset($this->config->bug->search['fields']['product']);
@@ -823,8 +831,9 @@ class productplan extends control
else
{
$this->config->bug->search['fields']['branch'] = $this->lang->product->branch;
$branchName = $this->loadModel('branch')->getById($plan->branch);
$branches = array('' => '', BRANCH_MAIN => $this->lang->branch->main, $plan->branch => $branchName);
$branchPairs = $this->dao->select('id, name')->from(TABLE_BRANCH)->where('id')->in($plan->branch)->fetchPairs();
$branches = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branchPairs;
$this->config->bug->search['params']['branch']['values'] = $branches;
}
$this->loadModel('search')->setSearchParams($this->config->bug->search);
@@ -891,13 +900,13 @@ class productplan extends control
}
/**
* Synchronize story when edit plan.
* Unlink story and bug when edit branch of plan.
* @param int $planID
* @param int $oldBranch
* @access protected
* @return void
*/
protected function syncStory($changes)
protected function unlinkOldBranch($changes)
{
foreach($changes as $planID => $changes)
{
@@ -913,38 +922,66 @@ class productplan extends control
}
}
$planStories = $this->loadModel('story')->getPlanStories($planID, 'all');
$planBugs = $this->loadModel('bug')->getPlanBugs($planID, 'all');
if($oldBranch)
{
foreach($planStories as $storyID => $story)
{
if($story->branch and $story->branch != $newBranch) $this->productplan->unlinkStory($storyID, $planID);
if($story->branch and strpos(",$newBranch,", ",$story->branch,") === false) $this->productplan->unlinkStory($storyID, $planID);
}
foreach($planBugs as $bugID => $bug)
{
if($bug->branch and strpos(",$newBranch,", ",$bug->branch,") === false) $this->productplan->unlinkBug($bugID, $planID);
}
}
}
}
/**
* AJAX: Get conflict story.
* AJAX: Get conflict story and bug.
*
* @param int $planID
* @param int $branch
* @access public
* @return void
*/
public function ajaxGetConflictStory($planID, $newBranch)
public function ajaxGetConflict($planID, $newBranch)
{
$plan = $this->productplan->getByID($planID);
$oldBranch = $plan->branch;
$planStories = $this->loadModel('story')->getPlanStories($planID, 'all');
$conflictStoryIdList = '';
if($newBranch == '') return;
$plan = $this->productplan->getByID($planID);
$oldBranch = $plan->branch;
$planStories = $this->loadModel('story')->getPlanStories($planID, 'all');
$planBugs = $this->loadModel('bug')->getPlanBugs($planID, 'all');
$conflictStoryCounts = 0;
$conflictBugCounts = 0;
if($oldBranch)
{
foreach($planStories as $storyID => $story)
{
if($story->branch and $story->branch != $newBranch) $conflictStoryIdList .= '[' . $storyID . ']';
if($story->branch and strpos(",$newBranch,", ",$story->branch,") === false) $conflictStoryCounts ++;
}
foreach($planBugs as $bugID => $bug)
{
if($bug->branch and strpos(",$newBranch,", ",$bug->branch,") === false) $conflictBugCounts ++;
}
}
if($conflictStoryIdList != '') printf($this->lang->story->confirmChangePlan, $conflictStoryIdList);
if($conflictStoryCounts and $conflictBugCounts)
{
printf($this->lang->productplan->confirmChangePlan, $conflictStoryCounts, $conflictBugCounts);
}
elseif($conflictStoryCounts)
{
printf($this->lang->productplan->confirmRemoveStory, $conflictStoryCounts);
}
elseif($conflictBugCounts)
{
printf($this->lang->productplan->confirmRemoveBug, $conflictBugCounts);
}
}
/**
@@ -967,12 +1004,56 @@ class productplan extends control
*
* @param int $productID
* @param int $branch
* @param int $planID
* @access public
* @return object
*/
public function ajaxGetTopPlan($productID, $branch = 0)
public function ajaxGetTopPlan($productID, $branch = 0, $planID = 0)
{
$parentPlanPairs = $this->productplan->getTopPlanPairs($productID, $branch);
return print(html::select('parent', array('0' => '') + $parentPlanPairs, '', 'class="form-control"'));
if(isset($parentPlanPairs[$planID])) unset($parentPlanPairs[$planID]);
return print(html::select('parent', array(0 => '') + $parentPlanPairs, 0, 'class="form-control"'));
}
/**
* AJAX: Get diff branches tips.
*
* @param int $productID
* @param int $parentID
* @param string $branches
* @access public
* @return void
*/
public function ajaxGetDiffBranchesTip($productID = 0, $parentID = 0, $branches = '')
{
if(empty($parentID) or empty($productID)) return;
/* If it has children, return. */
$parentBranch = $this->productplan->getByID($parentID);
if($parentBranch->parent == '-1') return;
/* Find diff branches between parent plan and child plan. */
$diffBranches = array();
$diffBranchesTip = '';
$product = $this->loadModel('product')->getByID($productID);
$branchPairs = $this->loadModel('branch')->getPairs($productID);
foreach(explode(',', $parentBranch->branch) as $parentBranchID)
{
if(empty($parentBranchID)) continue;
if(strpos(",$branches,", ",$parentBranchID,") === false)
{
$diffBranches[$parentBranchID] = $parentBranchID;
$diffBranchesTip .= "{$branchPairs[$parentBranchID]},";
}
}
if(empty($diffBranchesTip)) return;
/* Find stories and bugs in diff branches. */
$unlinkStories = $this->dao->select('*')->from(TABLE_STORY)->where('branch')->in($diffBranches)->andWhere("CONCAT(',', plan, ',')")->like("%,{$parentID},%")->fetchAll('id');
$unlinkBugs = $this->dao->select('*')->from(TABLE_BUG)->where('branch')->in($diffBranches)->andWhere('plan')->eq($parentID)->fetchAll('id');
if(empty($unlinkStories) and empty($unlinkBugs)) return;
$this->lang->productplan->diffBranchesTip = str_replace('@branch@', $this->lang->product->branchName[$product->type], $this->lang->productplan->diffBranchesTip);
printf($this->lang->productplan->diffBranchesTip, trim($diffBranchesTip, ','));
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
.table td.content div {height: 25px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; float: left; max-width: calc(100% - 20px);}
.table td.content .more {position: absolute; right: 16px;}
.table td.content .more .icon {color: #838a9d;}
td.c-branch {overflow: hidden; text-align: left !important; text-overflow: ellipsis; white-space: nowrap;}
td.c-branch {overflow: hidden; text-align: left !important; white-space: nowrap;}
.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;}
+4 -4
View File
@@ -24,17 +24,17 @@ function changeDate(planID)
* Get conflict stories.
*
* @param int $planID
* @param int $branch
* @access public
* @return void
*/
function getConflictStories(planID, branch)
function getConflictStories(planID)
{
$.get(createLink('productplan', 'ajaxGetConflictStory', 'planID=' + planID + '&newBranch=' + branch), function(conflictStories)
var newBranch = $('#branch' + planID).val() ? $('#branch' + planID).val().toString() : '';
$.get(createLink('productplan', 'ajaxGetConflict', 'planID=' + planID + '&newBranch=' + newBranch), function(conflictStories)
{
if(conflictStories != '' && !confirm(conflictStories))
{
$('#branch' + planID).val(oldBranch[planID]);
$('#branch' + planID).val(oldBranch[planID].split(','));
$('#branch' + planID).trigger("chosen:updated");
}
});
+23 -3
View File
@@ -65,9 +65,14 @@ $('#future').on('change', function()
$('#branch').change(function()
{
var branchID = $(this).val();
var lastPlanLink = createLink('productplan', 'ajaxGetLast', "productID=" + productID + "&branch=" + branchID);
var topPlanLink = createLink('productplan', 'ajaxGetTopPlan', "productID=" + productID + "&branch=" + branchID);
if(parentPlanID) return;
var branchIdList = $(this).val();
if(!branchIdList) return;
var branchIdList = branchIdList.toString();
var lastPlanLink = createLink('productplan', 'ajaxGetLast', "productID=" + productID + "&branch=" + branchIdList);
var topPlanLink = createLink('productplan', 'ajaxGetTopPlan', "productID=" + productID + "&branch=" + branchIdList);
$.post(lastPlanLink, function(data)
{
@@ -83,3 +88,18 @@ $('#branch').change(function()
$('#parent').chosen();
})
});
$('#submit').click(function()
{
var parentPlan = $('#parent').val();
var branches = $('#branch').val();
if(parentPlan != 0 && branches)
{
link = createLink('productplan', 'ajaxGetDiffBranchesTip', "produtID=" + productID + "&parentID=" + parentPlan + "&branches=" + branches.toString());
$.post(link, function(diffBranchesTip)
{
if((diffBranchesTip != '' && confirm(diffBranchesTip)) || !diffBranchesTip) $('form#dataform').submit();
});
return false;
}
});
+26 -14
View File
@@ -17,34 +17,32 @@ function convertStringToDate(dateString)
* Get conflict stories.
*
* @param int $planID
* @param int $branch
* @access public
* @return void
*/
function getConflictStories(planID, branch)
function getConflictStories(planID)
{
$.get(createLink('productplan', 'ajaxGetConflictStory', 'planID=' + planID + '&newBranch=' + branch), function(conflictStories)
var newBranch = $('#branch').val() ? $('#branch').val().toString() : '';
$.get(createLink('productplan', 'ajaxGetConflict', 'planID=' + planID + '&newBranch=' + newBranch), function(conflictStories)
{
if(conflictStories != '')
{
var result = confirm(conflictStories) ? true : false;
if(!result)
{
$('#branch').val(oldBranch[planID]);
newBranch = oldBranch[planID];
$('#branch').val(oldBranch[planID].split(','));
$('#branch').trigger("chosen:updated");
}
}
if(conflictStories == '' || result)
var link = createLink('productplan', 'ajaxGetTopPlan', "productID=" + productID + "&branch=" + newBranch + "&planID=" + planID);
$.post(link, function(data)
{
var link = createLink('productplan', 'ajaxGetTopPlan', "productID=" + productID + "&branch=" + branch);
$.post(link, function(data)
{
$('#parent').replaceWith(data);
$('#parent_chosen').remove();
$('#parent').chosen();
})
}
$('#parent').replaceWith(data);
$('#parent_chosen').remove();
$('#parent').chosen();
})
});
}
@@ -88,5 +86,19 @@ $('#future').on('change', function()
}
});
$('#future').change();
$('#submit').click(function()
{
var parentPlan = $('#parent').val();
var branches = $('#branch').val();
if(parentPlan != 0 && branches)
{
link = createLink('productplan', 'ajaxGetDiffBranchesTip', "produtID=" + productID + "&parentID=" + parentPlan + "&branches=" + branches.toString());
$.post(link, function(diffBranchesTip)
{
if((diffBranchesTip != '' && confirm(diffBranchesTip)) || !diffBranchesTip) $('form#dataform').submit();
});
return false;
}
});
+4
View File
@@ -75,6 +75,9 @@ $lang->productplan->projectNotEmpty = 'Project cannot be empty.';
$lang->productplan->nextStep = "Next step";
$lang->productplan->summary = "Total: <strong>%s</strong>, Parents: <strong>%s</strong>, Children: <strong>%s</strong>,Independent: <strong>%s</strong>.";
$lang->productplan->checkedSummary = "Seleted: <strong>%total%</strong>, Parents: <strong>%parent%</strong>, Children: <strong>%child%</strong>, Independent: <strong>%independent%</strong>.";
$lang->productplan->confirmChangePlan = "After the branch is unlinked, %s {$lang->SRCommon} and %s bugs under the branch will be removed from the plan at the same time, so still want to unassociate?";
$lang->productplan->confirmRemoveStory = "After the branch is unlinked, %s {$lang->SRCommon} under the branch will be removed from the plan at the same time, so still want to unassociate?";
$lang->productplan->confirmRemoveBug = "After the branch is unlinked, %s bugs under the branch will be removed from the plan at the same time, so still want to unassociate?";
$lang->productplan->id = 'ID';
$lang->productplan->product = $lang->productCommon;
@@ -136,6 +139,7 @@ $lang->productplan->beginGreaterChildTip = "The start date of the parent plan[%s
$lang->productplan->endLetterChildTip = "The completion date of the parent plan[%s]: %s, cannot be less than the completion date of the child plan: %s.";
$lang->productplan->beginLetterParentTip = "The start date of the child plan[%s]: %s, cannot be less than the start date of the parent plan: %s.";
$lang->productplan->endGreaterParentTip = "The completion date of the child plan[%s]: %s, cannot be greater than the completion date of the parent plan: %s.";
$lang->productplan->diffBranchesTip = "The @branch@ %s of parent plan is not linked with the child plan. @branch@'s stories and bugs whill be removed from the plan. Do you want to save?";
$lang->productplan->featureBar['browse']['all'] = 'Alle';
$lang->productplan->featureBar['browse']['undone'] = 'Undone';
+4
View File
@@ -75,6 +75,9 @@ $lang->productplan->projectNotEmpty = 'Project cannot be empty.';
$lang->productplan->nextStep = "Next step";
$lang->productplan->summary = "Total: <strong>%s</strong>, Parents: <strong>%s</strong>, Children: <strong>%s</strong>,Independent: <strong>%s</strong>.";
$lang->productplan->checkedSummary = "Seleted: <strong>%total%</strong>, Parents: <strong>%parent%</strong>, Children: <strong>%child%</strong>, Independent: <strong>%independent%</strong>.";
$lang->productplan->confirmChangePlan = "After the branch is unlinked, %s {$lang->SRCommon} and %s bugs under the branch will be removed from the plan at the same time, so still want to unassociate?";
$lang->productplan->confirmRemoveStory = "After the branch is unlinked, %s {$lang->SRCommon} under the branch will be removed from the plan at the same time, so still want to unassociate?";
$lang->productplan->confirmRemoveBug = "After the branch is unlinked, %s bugs under the branch will be removed from the plan at the same time, so still want to unassociate?";
$lang->productplan->id = 'ID';
$lang->productplan->product = $lang->productCommon;
@@ -136,6 +139,7 @@ $lang->productplan->beginGreaterChildTip = "The start date of the parent plan[%s
$lang->productplan->endLetterChildTip = "The completion date of the parent plan[%s]: %s, cannot be less than the completion date of the child plan: %s.";
$lang->productplan->beginLetterParentTip = "The start date of the child plan[%s]: %s, cannot be less than the start date of the parent plan: %s.";
$lang->productplan->endGreaterParentTip = "The completion date of the child plan[%s]: %s, cannot be greater than the completion date of the parent plan: %s.";
$lang->productplan->diffBranchesTip = "The @branch@ %s of parent plan is not linked with the child plan. @branch@'s stories and bugs whill be removed from the plan. Do you want to save?";
$lang->productplan->featureBar['browse']['all'] = 'All';
$lang->productplan->featureBar['browse']['undone'] = 'Undone';
+4
View File
@@ -75,6 +75,9 @@ $lang->productplan->projectNotEmpty = 'Project cannot be empty.';
$lang->productplan->nextStep = "Next step";
$lang->productplan->summary = "Total: <strong>%s</strong>, Parents: <strong>%s</strong>, Children: <strong>%s</strong>,Independent: <strong>%s</strong>.";
$lang->productplan->checkedSummary = "Seleted: <strong>%total%</strong>, Parents: <strong>%parent%</strong>, Children: <strong>%child%</strong>, Independent: <strong>%independent%</strong>.";
$lang->productplan->confirmChangePlan = "After the branch is unlinked, %s {$lang->SRCommon} and %s bugs under the branch will be removed from the plan at the same time, so still want to unassociate?";
$lang->productplan->confirmRemoveStory = "After the branch is unlinked, %s {$lang->SRCommon} under the branch will be removed from the plan at the same time, so still want to unassociate?";
$lang->productplan->confirmRemoveBug = "After the branch is unlinked, %s bugs under the branch will be removed from the plan at the same time, so still want to unassociate?";
$lang->productplan->id = 'ID';
$lang->productplan->product = $lang->productCommon;
@@ -136,6 +139,7 @@ $lang->productplan->beginGreaterChildTip = "The start date of the parent plan[%s
$lang->productplan->endLetterChildTip = "The completion date of the parent plan[%s]: %s, cannot be less than the completion date of the child plan: %s.";
$lang->productplan->beginLetterParentTip = "The start date of the child plan[%s]: %s, cannot be less than the start date of the parent plan: %s.";
$lang->productplan->endGreaterParentTip = "The completion date of the child plan[%s]: %s, cannot be greater than the completion date of the parent plan: %s.";
$lang->productplan->diffBranchesTip = "The @branch@ %s of parent plan is not linked with the child plan. @branch@'s stories and bugs whill be removed from the plan. Do you want to save?";
$lang->productplan->featureBar['browse']['all'] = 'Tous';
$lang->productplan->featureBar['browse']['undone'] = 'Undone';
+4
View File
@@ -75,6 +75,9 @@ $lang->productplan->projectNotEmpty = '所属项目不能为空。';
$lang->productplan->nextStep = "下一步";
$lang->productplan->summary = "本页共 <strong>%s</strong> 个计划,父计划 <strong>%s</strong>,子计划 <strong>%s</strong>,独立计划 <strong>%s</strong>。";
$lang->productplan->checkedSummary = "共选中 <strong>%total%</strong> 个计划,父计划 <strong>%parent%</strong>,子计划 <strong>%child%</strong>,独立计划 <strong>%independent%</strong>。";
$lang->productplan->confirmChangePlan = "分支解除关联后,分支下的%s个{$lang->SRCommon}和%s个Bug将同步从计划中移除,是否解除?";
$lang->productplan->confirmRemoveStory = "分支解除关联后,分支下的%s个{$lang->SRCommon}将同步从计划中移除,是否解除?";
$lang->productplan->confirmRemoveBug = "分支解除关联后,分支下的%s个Bug将同步从计划中移除,是否解除?";
$lang->productplan->id = '编号';
$lang->productplan->product = $lang->productCommon;
@@ -136,6 +139,7 @@ $lang->productplan->beginGreaterChildTip = "父计划[%s]的开始日期:%s,
$lang->productplan->endLetterChildTip = "父计划[%s]的完成日期:%s,不能小于子计划的完成日期: %s";
$lang->productplan->beginLetterParentTip = "子计划[%s]的开始日期:%s,不能小于父计划的开始日期: %s";
$lang->productplan->endGreaterParentTip = "子计划[%s]的完成日期:%s,不能大于父计划的完成日期: %s";
$lang->productplan->diffBranchesTip = "父计划的@branch@ %s 未被子计划关联,对应@branch@的需求和bug将自动从计划中移除,是否保存?";
$lang->productplan->featureBar['browse']['all'] = '全部';
$lang->productplan->featureBar['browse']['undone'] = '未完成';
+228 -104
View File
@@ -52,15 +52,28 @@ class productplanModel extends model
* @access public
* @return object
*/
public function getLast($productID, $branch = 0, $parent = 0)
public function getLast($productID, $branch = '', $parent = 0)
{
$branchQuery = '';
if($branch !== '')
{
$branchQuery .= '(';
$branchCount = count(explode(',', $branch));
foreach(explode(',', $branch) as $index => $branchID)
{
$branchQuery .= "CONCAT(',', branch, ',') LIKE '%,$branchID,%'";
if($index < $branchCount - 1) $branchQuery .= ' AND ';
}
$branchQuery .= ')';
}
return $this->dao->select('*')->from(TABLE_PRODUCTPLAN)
->where('deleted')->eq(0)
->beginIF($parent <= 0)->andWhere('parent')->le((int)$parent)->fi()
->beginIF($parent > 0)->andWhere('parent')->eq((int)$parent)->fi()
->andWhere('product')->eq((int)$productID)
->andWhere('end')->ne($this->config->productplan->future)
->andWhere('branch')->eq($branch)
->beginIF($branch !== '' and !empty($branchQuery))->andWhere($branchQuery)->fi()
->orderBy('end desc')
->limit(1)
->fetch();
@@ -216,9 +229,22 @@ class productplanModel extends model
*/
public function getTopPlanPairs($productID, $branch = '', $exclude = '')
{
$branchQuery = '';
if($branch !== '')
{
$branchQuery .= '(';
$branchCount = count(explode(',', $branch));
foreach(explode(',', $branch) as $index => $branchID)
{
$branchQuery .= "CONCAT(',', branch, ',') LIKE '%,$branchID,%'";
if($index < $branchCount - 1) $branchQuery .= ' AND ';
}
$branchQuery .= ')';
}
$planPairs = $this->dao->select("id,title")->from(TABLE_PRODUCTPLAN)
->where('product')->eq($productID)
->beginIF($branch !== '')->andWhere('branch')->eq($branch)->fi()
->beginIF($branch !== '' and !empty($branchQuery))->andWhere($branchQuery)->fi()
->andWhere('parent')->le(0)
->andWhere('deleted')->eq(0)
->beginIF($exclude)->andWhere('status')->notin($exclude)
@@ -240,28 +266,43 @@ class productplanModel extends model
*/
public function getPairs($product = 0, $branch = '', $param = '', $skipParent = false)
{
$date = date('Y-m-d');
$plans = $this->dao->select('t1.id,t1.title,t1.parent,t1.begin,t1.end,t2.name as branchName,t3.type as productType')->from(TABLE_PRODUCTPLAN)->alias('t1')
->leftJoin(TABLE_BRANCH)->alias('t2')->on('t2.id=t1.branch')
$this->app->loadLang('branch');
$date = date('Y-m-d');
$branchQuery = '';
if($branch !== '' and $branch != 'all')
{
$branchQuery .= '(';
$branchCount = count(explode(',', $branch));
foreach(explode(',', $branch) as $index => $branchID)
{
$branchQuery .= "FIND_IN_SET('$branchID', t1.branch)";
if($index < $branchCount - 1) $branchQuery .= ' OR ';
}
$branchQuery .= ')';
}
$plans = $this->dao->select('t1.id,t1.title,t1.parent,t1.begin,t1.end,t3.type as productType,t1.branch')->from(TABLE_PRODUCTPLAN)->alias('t1')
->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t3.id=t1.product')
->where('t1.product')->in($product)
->andWhere('t1.deleted')->eq(0)
->beginIF($branch !== '' and $branch != 'all')->andWhere('t1.branch')->in("0,$branch")->fi()
->beginIF(!empty($branchQuery))->andWhere($branchQuery)->fi()
->beginIF(strpos($param, 'unexpired') !== false)->andWhere('t1.end')->ge($date)->fi()
->beginIF(strpos($param, 'noclosed') !== false)->andWhere('t1.status')->ne('closed')->fi()
->orderBy('t1.begin desc')
->fetchAll('id');
$plans = $this->reorder4Children($plans);
$plans = $this->relationBranch($plans);
$planPairs = array();
$this->app->loadLang('branch');
foreach($plans as $plan)
{
if($skipParent and $plan->parent == '-1') continue;
if($plan->parent > 0 and isset($plans[$plan->parent])) $plan->title = $plans[$plan->parent]->title . ' /' . $plan->title;
$planPairs[$plan->id] = $plan->title . " [{$plan->begin} ~ {$plan->end}]";
if($plan->begin == $this->config->productplan->future and $plan->end == $this->config->productplan->future) $planPairs[$plan->id] = $plan->title . ' ' . $this->lang->productplan->future;
if($plan->productType != 'normal') $planPairs[$plan->id] = ($plan->branchName ? $plan->branchName : $this->lang->branch->main) . ' / ' . $planPairs[$plan->id];
if($plan->productType != 'normal') $planPairs[$plan->id] = $planPairs[$plan->id] . ' / ' . ($plan->branchName ? $plan->branchName : $this->lang->branch->main);
}
return array('' => '') + $planPairs;
}
@@ -280,12 +321,26 @@ class productplanModel extends model
$date = date('Y-m-d');
$param = strtolower($param);
$branch = strpos($param, 'withmainplan') !== false ? "0,$branch" : $branch;
$branchQuery = '';
if($branch !== '' and $branch != 'all')
{
$branchQuery .= '(';
$branchCount = count(explode(',', $branch));
foreach(explode(',', $branch) as $index => $branchID)
{
$branchQuery .= "FIND_IN_SET('$branchID', branch)";
if($index < $branchCount - 1) $branchQuery .= ' OR ';
}
$branchQuery .= ')';
}
$plans = $this->dao->select('id,title,parent,begin,end')->from(TABLE_PRODUCTPLAN)
->where('product')->in($product)
->andWhere('deleted')->eq(0)
->beginIF(strpos($param, 'unexpired') !== false)->andWhere('end')->ge($date)->fi()
->beginIF(strpos($param, 'noclosed') !== false)->andWhere('status')->ne('closed')->fi()
->beginIF($branch !== 'all' and $branch !== '')->andWhere("branch")->in($branch)->fi()
->beginIF($branch !== 'all' and $branch !== '')->andWhere($branchQuery)->fi()
->orderBy('begin desc')
->fetchAll('id');
@@ -310,7 +365,7 @@ class productplanModel extends model
/**
* Get plans for products
*
* @param int $products
* @param array $products
* @access public
* @return void
*/
@@ -348,32 +403,39 @@ class productplanModel extends model
{
$date = date('Y-m-d');
$param = strtolower($param);
$plans = $this->dao->select('*')->from(TABLE_PRODUCTPLAN)
->where('deleted')->eq(0)
->beginIF($products)->andWhere('product')->in($products)->fi()
->beginIF(strpos($param, 'unexpired') !== false)->andWhere('end')->ge($date)->fi()
->orderBy($orderBy)
$plans = $this->dao->select('t1.*,t2.type as productType')->from(TABLE_PRODUCTPLAN)->alias('t1')
->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t2.id=t1.product')
->where('t1.deleted')->eq(0)
->beginIF($products)->andWhere('t1.product')->in($products)->fi()
->beginIF(strpos($param, 'unexpired') !== false)->andWhere('t1.end')->ge($date)->fi()
->orderBy('t1.' . $orderBy)
->fetchAll('id');
if(!empty($plans) and $field == 'name') $plans = $this->reorder4Children($plans);
$plans = $this->relationBranch($plans);
$parentTitle = array();
$planGroup = array();
foreach($plans as $plan)
{
if(!isset($planGroup[$plan->product][$plan->branch])) $planGroup[$plan->product][$plan->branch] = array('' => '');
if($plan->parent == '-1' and strpos($param, 'skipparent') !== false) continue;
if($field == 'name')
foreach(explode(',', $plan->branch) as $branch)
{
if($plan->parent > 0 and isset($plans[$plan->parent])) $plan->title = $plans[$plan->parent]->title . ' /' . $plan->title;
$planGroup[$plan->product][$plan->branch][$plan->id] = $plan->title . " [{$plan->begin} ~ {$plan->end}]";
if($plan->begin == $this->config->productplan->future and $plan->end == $this->config->productplan->future) $planGroup[$plan->product][$plan->branch][$plan->id] = $plan->title . ' ' . $this->lang->productplan->future;
}
else
{
$planGroup[$plan->product][$plan->branch][$plan->id] = $plan;
if(!isset($planGroup[$plan->product][$branch])) $planGroup[$plan->product][$branch] = array('' => '');
if($plan->parent == '-1' and strpos($param, 'skipparent') !== false) continue 2;
if($field == 'name')
{
if($plan->parent > 0 and isset($plans[$plan->parent])) $plan->title = $plans[$plan->parent]->title . ' /' . $plan->title;
$planGroup[$plan->product][$branch][$plan->id] = $plan->title . " [{$plan->begin} ~ {$plan->end}]";
if($plan->begin == $this->config->productplan->future and $plan->end == $this->config->productplan->future) $planGroup[$plan->product][$branch][$plan->id] = $plan->title . ' ' . $this->lang->productplan->future;
if($plan->productType != 'normal') $planGroup[$plan->product][$branch][$plan->id] = $planGroup[$plan->product][$branch][$plan->id] . ' / ' . ($plan->branchName ? $plan->branchName : $this->lang->branch->main);
}
else
{
$planGroup[$plan->product][$branch][$plan->id] = $plan;
}
}
}
return $planGroup;
@@ -429,10 +491,13 @@ class productplanModel extends model
$planPairs = array();
foreach($plans as $planID => $plan)
{
if($skipParent and $plan->parent == '-1') continue;
foreach(explode(',', $plan->branch) as $branch)
{
if($skipParent and $plan->parent == '-1') continue 2;
if($plan->parent > 0 and isset($plans[$plan->parent])) $plan->title = $plans[$plan->parent]->title . ' /' . $plan->title;
$planPairs[$plan->branch][$planID] = $plan->title . ' [' . $plan->begin . '~' . $plan->end . ']';
if($plan->parent > 0 and isset($plans[$plan->parent])) $plan->title = $plans[$plan->parent]->title . ' /' . $plan->title;
$planPairs[$branch][$planID] = $plan->title . ' [' . $plan->begin . '~' . $plan->end . ']';
}
}
return $planPairs;
}
@@ -450,9 +515,18 @@ class productplanModel extends model
->setIF($this->post->future || empty($_POST['end']), 'end', $this->config->productplan->future)
->setDefault('createdBy', $this->app->user->account)
->setDefault('createdDate', helper::now())
->setDefault('branch', 0)
->join('branch', ',')
->remove('delta,uid,future')
->get();
$product = $this->loadModel('product')->getByID($plan->product);
if($product->type != 'normal' and !isset($_POST['branch']))
{
$this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
dao::$errors['branch'] = sprintf($this->lang->error->notempty, $this->lang->product->branch);
}
if($plan->parent > 0)
{
$parentPlan = $this->getByID($plan->parent);
@@ -477,8 +551,7 @@ class productplanModel extends model
if(dao::isError()) return false;
$plan = $this->loadModel('file')->processImgURL($plan, $this->config->productplan->editor->create['id'], $this->post->uid);
$this->dao->insert(TABLE_PRODUCTPLAN)
->data($plan)
$this->dao->insert(TABLE_PRODUCTPLAN)->data($plan)
->autoCheck()
->batchCheck($this->config->productplan->create->requiredFields, 'notempty')
->checkIF(!$this->post->future && !empty($_POST['begin']) && !empty($_POST['end']), 'end', 'ge', $plan->begin)
@@ -489,24 +562,10 @@ class productplanModel extends model
$planID = $this->dao->lastInsertID();
$this->file->updateObjectID($this->post->uid, $planID, 'plan');
$this->loadModel('score')->create('productplan', 'create', $planID);
if(!empty($plan->parent))
if(!empty($plan->parent) and $parentPlan->parent == '0')
{
if($parentPlan->parent == '0')
{
$this->dao->update(TABLE_PRODUCTPLAN)->set('parent')->eq('-1')->where('id')->eq($plan->parent)->andWhere('parent')->eq('0')->exec();
/* Transfer stories and bugs linked with the parent plan to the child plan. */
$this->dao->update(TABLE_PLANSTORY)->set('plan')->eq($planID)->where('plan')->eq($plan->parent)->exec();
$this->dao->update(TABLE_BUG)->set('plan')->eq($planID)->where('plan')->eq($plan->parent)->exec();
$stories = $this->dao->select('*')->from(TABLE_STORY)->where("CONCAT(',', plan, ',')")->like("%,{$plan->parent},%")->fetchAll('id');
foreach($stories as $storyID => $story)
{
$storyPlan = str_replace(",{$plan->parent},", ",$planID,", ",$story->plan,");
$storyPlan = trim($storyPlan, ',');
$this->dao->update(TABLE_STORY)->set('plan')->eq($storyPlan)->where('id')->eq($storyID)->exec();
}
}
$plan->id = $planID;
$this->transferStoriesAndBugs($plan);
}
return $planID;
}
@@ -525,10 +584,19 @@ class productplanModel extends model
$plan = fixer::input('post')->stripTags($this->config->productplan->editor->edit['id'], $this->config->allowedTags)
->setIF($this->post->future or empty($_POST['begin']), 'begin', $this->config->productplan->future)
->setIF($this->post->future or empty($_POST['end']), 'end', $this->config->productplan->future)
->setDefault('branch', 0)
->join('branch', ',')
->add('id', $planID)
->remove('delta,uid,future')
->get();
$product = $this->loadModel('product')->getByID($oldPlan->product);
if($product->type != 'normal' and $oldPlan->parent != -1 and !isset($_POST['branch']))
{
$this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
dao::$errors['branch'] = sprintf($this->lang->error->notempty, $this->lang->product->branch);
}
$parentPlan = $this->getByID($plan->parent);
$futureTime = $this->config->productplan->future;
@@ -545,9 +613,9 @@ class productplanModel extends model
}
elseif($oldPlan->parent == -1 and ($plan->begin != $futureTime or $plan->end != $futureTime))
{
$childPlans = $this->getChildren($planID);
$minBegin = $plan->begin;
$maxEnd = $plan->end;
$childPlans = $this->getChildren($planID);
$minBegin = $plan->begin;
$maxEnd = $plan->end;
foreach($childPlans as $childID => $childPlan)
{
$childPlan = isset($plans[$childID]) ? $plans[$childID] : $childPlan;
@@ -560,8 +628,7 @@ class productplanModel extends model
if(dao::isError()) return false;
$plan = $this->loadModel('file')->processImgURL($plan, $this->config->productplan->editor->edit['id'], $this->post->uid);
$this->dao->update(TABLE_PRODUCTPLAN)
->data($plan)
$this->dao->update(TABLE_PRODUCTPLAN)->data($plan)
->autoCheck()
->batchCheck($this->config->productplan->edit->requiredFields, 'notempty')
->checkIF(!$this->post->future && !empty($_POST['begin']) && !empty($_POST['end']), 'end', 'ge', $plan->begin)
@@ -576,25 +643,7 @@ class productplanModel extends model
if(!dao::isError())
{
$this->file->updateObjectID($this->post->uid, $planID, 'plan');
if(!empty($plan->parent))
{
if($parentPlan->parent == '0')
{
$this->dao->update(TABLE_PRODUCTPLAN)->set('parent')->eq('-1')->where('id')->eq($plan->parent)->andWhere('parent')->eq('0')->exec();
/* Transfer stories and bugs linked with the parent plan to the child plan. */
$this->dao->update(TABLE_PLANSTORY)->set('plan')->eq($planID)->where('plan')->eq($plan->parent)->exec();
$this->dao->update(TABLE_BUG)->set('plan')->eq($planID)->where('plan')->eq($plan->parent)->exec();
$stories = $this->dao->select('*')->from(TABLE_STORY)->where("CONCAT(',', plan, ',')")->like("%,{$plan->parent},%")->fetchAll('id');
foreach($stories as $storyID => $story)
{
$storyPlan = str_replace(",{$plan->parent},", ",$planID,", ",$story->plan,");
$storyPlan = trim($storyPlan, ',');
$this->dao->update(TABLE_STORY)->set('plan')->eq($storyPlan)->where('id')->eq($storyID)->exec();
}
}
}
if(!empty($plan->parent) and $parentPlan->parent == '0') $this->transferStoriesAndBugs($plan);
return common::createChanges($oldPlan, $plan);
}
}
@@ -707,13 +756,20 @@ class productplanModel extends model
$plans = array();
$extendFields = $this->getFlowExtendFields();
$product = $this->loadModel('product')->getByID($productID);
foreach($data->id as $planID)
{
if($product->type != 'normal' and $oldPlans[$planID]->parent != '-1' and !isset($data->branch[$planID]))
{
$this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
return helper::end(js::error(sprintf($this->lang->error->notempty, $this->lang->product->branch)));
}
$isFuture = isset($data->future[$planID]) ? true : false;
if(isset($data->status[$planID]) and $data->status[$planID] != 'wait') $isFuture = false;
$plan = new stdclass();
$plan->branch = isset($data->branch[$planID]) ? $data->branch[$planID] : $oldPlans[$planID]->branch;
$plan->branch = isset($data->branch[$planID]) ? join(',', $data->branch[$planID]) : $oldPlans[$planID]->branch;
$plan->title = $data->title[$planID];
$plan->begin = isset($data->begin[$planID]) ? $data->begin[$planID] : '';
$plan->end = isset($data->end[$planID]) ? $data->end[$planID] : '';
@@ -721,8 +777,8 @@ class productplanModel extends model
$plan->parent = $oldPlans[$planID]->parent;
$plan->id = $planID;
if(empty($plan->title)) return print(js::alert(sprintf($this->lang->productplan->errorNoTitle, $planID)));
if($plan->begin > $plan->end and !empty($plan->end)) return print(js::alert(sprintf($this->lang->productplan->beginGeEnd, $planID)));
if(empty($plan->title)) return helper::end(js::alert(sprintf($this->lang->productplan->errorNoTitle, $planID)));
if($plan->begin > $plan->end and !empty($plan->end)) return helper::end(js::alert(sprintf($this->lang->productplan->beginGeEnd, $planID)));
if($plan->begin == '') $plan->begin = $this->config->productplan->future;
if($plan->end == '') $plan->end = $this->config->productplan->future;
@@ -746,14 +802,14 @@ class productplanModel extends model
/* Determine whether the begin and end dates of the parent plan and the child plan are correct. */
if($parentID > 0)
{
$parent = isset($plans[$parentID]) ? $plans[$parentID] : $this->getByID($parentID);
$parent = isset($plans[$parentID]) ? $plans[$parentID] : $this->getByID($parentID);
if($parent->begin != $this->config->productplan->future and $plan->begin != $this->config->productplan->future and $plan->begin < $parent->begin)
{
return print(js::alert(sprintf($this->lang->productplan->beginLetterParentTip, $planID, $plan->begin, $parent->begin)));
return helper::end(js::alert(sprintf($this->lang->productplan->beginLetterParentTip, $planID, $plan->begin, $parent->begin)));
}
elseif($parent->end != $this->config->productplan->future and $plan->end != $this->config->productplan->future and $plan->end > $parent->end)
{
return print(js::alert(sprintf($this->lang->productplan->endGreaterParentTip, $planID, $plan->end, $parent->end)));
return helper::end(js::alert(sprintf($this->lang->productplan->endGreaterParentTip, $planID, $plan->end, $parent->end)));
}
}
elseif($parentID == -1 and $plan->begin != $this->config->productplan->future)
@@ -767,8 +823,8 @@ class productplanModel extends model
if($childPlan->begin < $minBegin and $minBegin != $this->config->productplan->future) $minBegin = $childPlan->begin;
if($childPlan->end > $maxEnd and $maxEnd != $this->config->productplan->future) $maxEnd = $childPlan->end;
}
if($minBegin < $plan->begin and $minBegin != $this->config->productplan->future) return print(js::alert(sprintf($this->lang->productplan->beginGreaterChildTip, $planID, $plan->begin, $minBegin)));
if($maxEnd > $plan->end and $maxEnd != $this->config->productplan->future) return print(js::alert(sprintf($this->lang->productplan->endLetterChildTip, $planID, $plan->end, $maxEnd)));
if($minBegin < $plan->begin and $minBegin != $this->config->productplan->future) return helper::end(js::alert(sprintf($this->lang->productplan->beginGreaterChildTip, $planID, $plan->begin, $minBegin)));
if($maxEnd > $plan->end and $maxEnd != $this->config->productplan->future) return helper::end(js::alert(sprintf($this->lang->productplan->endLetterChildTip, $planID, $plan->end, $maxEnd)));
}
$change = common::createChanges($oldPlans[$planID], $plan);
@@ -776,7 +832,7 @@ class productplanModel extends model
{
if($parentID > 0 and !isset($parents[$parentID])) $parents[$parentID] = $parentID;
$this->dao->update(TABLE_PRODUCTPLAN)->data($plan)->autoCheck()->checkFlow()->where('id')->eq($planID)->exec();
if(dao::isError()) return print(js::error(dao::getError()));
if(dao::isError()) return helper::end(js::error(dao::getError()));
$changes[$planID] = $change;
}
}
@@ -918,27 +974,9 @@ class productplanModel extends model
if(strpos(",$story->plan,", ",{$planID},") !== false) continue;
/* Update the plan linked with the story and the order of the story in the plan. */
if($this->session->currentProductType == 'normal' or $story->branch != 0 or empty($story->plan))
{
$this->dao->update(TABLE_STORY)->set("plan")->eq($planID)->where('id')->eq((int)$storyID)->exec();
$this->dao->update(TABLE_STORY)->set("plan")->eq($planID)->where('id')->eq((int)$storyID)->exec();
$this->story->updateStoryOrderOfPlan($storyID, $planID, $story->plan);
}
else
{
$branchPlanPairs = $this->dao->select('branch, id as plan')->from(TABLE_PRODUCTPLAN)
->where('product')->eq($story->product)
->andWhere('id')->in($story->plan)
->fetchPairs();
if(isset($branchPlanPairs[$plan->branch])) $branchPlanPairs[$plan->branch] = $planID;
$plansOfStory = implode(',', $branchPlanPairs);
$plansOfStory = trim($plansOfStory, ',');
$this->dao->update(TABLE_STORY)->set("plan")->eq($plansOfStory)->where('id')->eq((int)$storyID)->exec();
$this->story->updateStoryOrderOfPlan($storyID, $planID);
}
$this->story->updateStoryOrderOfPlan($storyID, $planID, $story->plan);
$this->action->create('story', $storyID, 'linked2plan', '', $planID);
$this->story->setStage($storyID);
@@ -1026,9 +1064,12 @@ class productplanModel extends model
$planStory = $this->loadModel('story')->getPlanStories($planID);
if(!empty($planStory))
{
$projectProducts = $this->loadModel('project')->getBranchesByProject($projectID);
foreach($planStory as $id => $story)
{
if($story->status == 'draft' or $story->status == 'reviewing')
$projectBranches = zget($projectProducts, $story->product, array());
if($story->status == 'draft' or $story->status == 'reviewing' or (!empty($story->branch) and !empty($projectBranches) and !isset($projectBranches[$story->branch])))
{
unset($planStory[$id]);
continue;
@@ -1080,6 +1121,46 @@ class productplanModel extends model
return $plans;
}
/**
* Get relation branch for plans.
*
* @param array $plans
* @access public
* @return array
*/
public function relationBranch($plans)
{
if(empty($plans)) return $plans;
$this->app->loadLang('branch');
$branchMap = $this->dao->select('id, name')->from(TABLE_BRANCH)
->where('status')->eq('active')
->andWhere('deleted')->eq('0')
->fetchPairs('id', 'name');
$branchMap[BRANCH_MAIN] = $this->lang->branch->main;
foreach($plans as &$plan)
{
if($plan->branch)
{
$branchName = '';
foreach(explode(',', $plan->branch) as $planBranch)
{
$branchName .= isset($branchMap[$planBranch]) ? $branchMap[$planBranch] : '';
$branchName .= ',';
}
$plan->branchName = trim($branchName, ',');
}
else
{
$plan->branchName = $this->lang->branch->main;
}
}
return $plans;
}
/**
* Judge an action is clickable or not.
*
@@ -1246,4 +1327,47 @@ class productplanModel extends model
if($product->type == 'normal') unset($this->config->productplan->search['fields']['branch']);
$this->loadModel('search')->setSearchParams($this->config->productplan->search);
}
/**
* Transfer stories and bugs to new plan.
*
* @param object $plan
* @access public
* @return void
*/
public function transferStoriesAndBugs($plan)
{
$this->dao->update(TABLE_PRODUCTPLAN)->set('parent')->eq('-1')->where('id')->eq($plan->parent)->andWhere('parent')->eq('0')->exec();
/* Transfer stories linked with the parent plan to the child plan. */
$stories = $this->dao->select('*')->from(TABLE_STORY)->where("CONCAT(',', plan, ',')")->like("%,{$plan->parent},%")->fetchAll('id');
$unlinkStories = array();
foreach($stories as $storyID => $story)
{
if(!empty($story->branch) and strpos(",$plan->branch,", ",$story->branch,") === false)
{
$unlinkStories[$storyID] = $storyID;
$storyPlan = str_replace(",{$plan->parent},", ',', ",$story->plan,");
}
else
{
$storyPlan = str_replace(",{$plan->parent},", ",$plan->id,", ",$story->plan,");
}
$storyPlan = trim($storyPlan, ',');
$this->dao->update(TABLE_STORY)->set('plan')->eq($storyPlan)->where('id')->eq($storyID)->exec();
}
if(!empty($unlinkStories)) $this->dao->delete()->from(TABLE_PLANSTORY)->where('plan')->eq($plan->parent)->andWhere('story')->in($unlinkStories)->exec();
$this->dao->update(TABLE_PLANSTORY)->set('plan')->eq($plan->id)->where('plan')->eq($plan->parent)->exec();
/* Transfer bugs linked with the parent plan to the child plan. */
$bugs = $this->dao->select('*')->from(TABLE_BUG)->where('plan')->eq($plan->parent)->fetchAll('id');
$unlinkBugs = array();
foreach($bugs as $bugID => $bug)
{
if(!empty($bug->branch) and strpos(",$plan->branch,", ",$bug->branch,") === false) $unlinkBugs[$bugID] = $bugID;
}
if(!empty($unlinkBugs)) $this->dao->update(TABLE_BUG)->set('plan')->eq(0)->where('plan')->eq($plan->parent)->andWhere('id')->in($unlinkBugs)->exec();
$this->dao->update(TABLE_BUG)->set('plan')->eq($plan->id)->where('plan')->eq($plan->parent)->exec();
}
}
+13 -3
View File
@@ -23,7 +23,7 @@
<tr class='text-center'>
<th class='c-id'><?php echo $lang->productplan->id?></th>
<?php if($product->type != 'normal'):?>
<th class='c-branch'><?php echo $lang->productplan->branch;?></th>
<th class='c-branch'><?php echo $lang->product->branch;?></th>
<?php endif;?>
<th class='required'><?php echo $lang->productplan->title?></th>
<th class='c-status'><?php echo $lang->productplan->status?></th>
@@ -43,8 +43,18 @@
<td class='text-center'><?php echo $plan->id . html::hidden("id[$plan->id]", $plan->id);?></td>
<?php if($product->type != 'normal'):?>
<td class='text-left'>
<?php $disabled = $plan->parent == -1 ? "disabled='disabled'" : '';?>
<?php echo html::select("branch[$plan->id]", $plan->parent == '-1' ? '' : $branchTagOption, $plan->branch, "onchange='getConflictStories($plan->id, this.value); 'class='form-control chosen' $disabled");?>
<?php
$disabled = $plan->parent == -1 ? "disabled='disabled'" : '';
$parentBranches = array();
if($plan->parent > 0 and isset($parentList[$plan->parent]))
{
foreach($branchTagOption as $branchID => $branchName)
{
if(strpos(",{$parentList[$plan->parent]->branch},", ",$branchID,") !== false) $parentBranches[$branchID] = $branchName;
}
}
echo html::select("branch[$plan->id][]", ($plan->parent > 0 and !empty($parentBranches)) ? $parentBranches : $branchTagOption, $plan->branch, "onchange='getConflictStories($plan->id); 'class='form-control chosen' multiple $disabled");
?>
</td>
<?php endif;?>
<td title='<?php echo $plan->title?>'><?php echo html::input("title[$plan->id]", $plan->title, "class='form-control'")?></td>
@@ -161,7 +161,9 @@
<td><?php echo zget($statusList, $plan->status)?></td>
<?php endif;?>
<?php if($this->session->currentProductType != 'normal'):?>
<td class='c-branch' title='<?php echo $branchOption[$plan->branch];?>'><?php if($plan->parent != '-1') echo $branchOption[$plan->branch];?></td>
<?php $planBranches = '';?>
<?php foreach(explode(',', $plan->branch) as $branchID) $planBranches .= $branchOption[$branchID] . ',';?>
<td class='c-branch' title='<?php echo trim($planBranches, ',');?>'><?php echo trim($planBranches, ',');?></td>
<?php endif;?>
<td><?php echo $plan->begin == $config->productplan->future ? $lang->productplan->future : $plan->begin;?></td>
<td><?php echo $plan->end == $config->productplan->future ? $lang->productplan->future : $plan->end;?></td>
+13 -4
View File
@@ -15,6 +15,7 @@
<?php js::set('weekend', $config->execution->weekend);?>
<?php js::set('productID', $productID);?>
<?php js::set('lastLang', $lang->productplan->last);?>
<?php js::set('parentPlanID', $parent);?>
<?php js::import($jsRoot . 'misc/date.js');?>
<div id='mainContent'class='main-content'>
<div class='center-block'>
@@ -35,13 +36,22 @@
<th><?php echo $lang->productplan->product;?></th>
<td class='muted'><?php echo $product->name;?></td><td></td><td></td>
</tr>
<?php endif;?>
<?php if($product->type != 'normal'):?>
<tr>
<th><?php echo $lang->product->branch;?></th>
<td><?php echo html::select('branch', $branches, $branch, "class='form-control chosen'");?></td><td></td><td></td>
<?php
if($parent)
{
foreach($branches as $branchID => $branchName)
{
if(strpos(",$parentPlan->branch,", ",$branchID,") === false) unset($branches[$branchID]);
}
}
?>
<td><?php echo html::select('branch[]', $branches, '', "class='form-control chosen' multiple required");?></td><td></td><td></td>
</tr>
<?php endif;?>
<?php endif;?>
<tr>
<th><?php echo $lang->productplan->title;?></th>
<td><?php echo html::input('title', '', "class='form-control' required");?></td>
@@ -50,7 +60,7 @@
<?php if(!$parent):?>
<tr>
<th><?php echo $lang->productplan->parent;?></th>
<td><?php echo html::select('parent', array('0' => '') + $parentPlanPairs, '', "class='form-control chosen'");?>
<td><?php echo html::select('parent', array(0 => '') + $parentPlanPairs, 0, "class='form-control chosen'");?>
</tr>
<?php endif;?>
<tr>
@@ -79,7 +89,6 @@
<?php echo html::backButton();?>
<?php echo html::hidden('product', $product->id);?>
<?php if($parent):?>
<?php echo html::hidden('branch', $parentPlan->branch);?>
<?php echo html::hidden('parent', $parent);?>
<?php endif;?>
</td>
+2 -2
View File
@@ -33,7 +33,7 @@
<?php if($product->type != 'normal' and $plan->parent != '-1'):?>
<tr>
<th><?php echo $lang->product->branch;?></th>
<td><?php echo html::select('branch', $branchTagOption, $plan->branch, "onchange='getConflictStories($plan->id, this.value); 'class='form-control'");?></td><td></td><td></td>
<td><?php echo html::select('branch[]', $branchTagOption, $plan->branch, "onchange='getConflictStories($plan->id);' class='form-control chosen' multiple");?></td><td></td><td></td>
</tr>
<?php endif;?>
<?php endif;?>
@@ -46,7 +46,7 @@
<?php else:?>
<tr>
<th><?php echo $lang->productplan->parent;?></th>
<td><?php echo html::select('parent', array('0' => '') + $parentPlanPairs, $plan->parent, "class='form-control chosen'");?>
<td><?php echo html::select('parent', array(0 => '') + $parentPlanPairs, $plan->parent, "class='form-control chosen'");?>
</tr>
<?php endif;?>
<tr>
+8 -2
View File
@@ -31,7 +31,9 @@
<div class='page-title'>
<span class='label label-id'><?php echo $plan->id;?></span>
<span title='<?php echo $plan->title;?>' class='text'><?php echo $plan->title;?></span>
<?php if($product->type !== 'normal') echo "<span title='{$lang->product->branchName[$product->type]}' class='label label-branch label-badge'>" . $branchOption[$branch] . '</span>';?>
<?php foreach(explode(',', $branch) as $branchID):?>
<?php if($product->type !== 'normal') echo "<span title='{$lang->product->branchName[$product->type]}' class='label label-branch label-badge'>" . $branchOption[$branchID] . '</span>';?>
<?php endforeach;?>
<span class='label label-info label-badge'>
<?php echo ($plan->begin == $config->productplan->future || $plan->end == $config->productplan->future) ? $lang->productplan->future : $plan->begin . '~' . $plan->end;?>
</span>
@@ -559,7 +561,11 @@
<?php if($product->type != 'normal'):?>
<tr>
<th><?php echo $lang->product->branch;?></th>
<td><?php echo $branchOption[$plan->branch];?></td>
<?php
$branches = '';
foreach(explode(',', $branch) as $branchID) $branches .= "{$branchOption[$branchID]},";
?>
<td><?php echo trim($branches, ',');?></td>
</tr>
<?php endif;?>
<tr>
+8 -17
View File
@@ -492,12 +492,9 @@ class project extends control
$planIdList = array();
foreach($_POST['plans'] as $plans)
{
foreach($plans as $planList)
foreach($plans as $planID)
{
foreach($planList as $planID)
{
$planIdList[$planID] = $planID;
}
$planIdList[$planID] = $planID;
}
}
@@ -587,10 +584,8 @@ class project extends control
if(!$copyProject->hasProduct) $shadow = 1;
foreach($products as $product)
{
foreach($product->branches as $branch)
{
$productPlans[$product->id][$branch] = $this->loadModel('productplan')->getPairs($product->id, $branch, 'noclosed', true);
}
$branches = implode(',', $product->branches);
$productPlans[$product->id] = $this->loadModel('productplan')->getPairs($product->id, $branches, 'noclosed', true);
}
}
@@ -722,9 +717,8 @@ class project extends control
$withProgram = $this->config->systemMode == 'ALM' ? true : false;
$linkedBranches = array();
$linkedBranchList = array();
$productPlans = array(0 => '');
$productPlans = array();
$branches = $this->project->getBranchesByProject($projectID);
$linkedProductIdList = empty($branches) ? '' : array_keys($branches);
$allProducts = $this->program->getProductPairs($project->parent, 'all', 'noclosed', '', 0, $withProgram);
@@ -743,11 +737,10 @@ class project extends control
if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->name;
foreach($branches[$productID] as $branchID => $branch)
{
$linkedBranchList[$branchID] = $branchID;
$linkedBranches[$productID][$branchID] = $branchID;
$linkedBranchList[$branchID] = $branchID;
if($branch != BRANCH_MAIN) $productPlans[$productID][$branchID] = isset($plans[$productID][BRANCH_MAIN]) ? $plans[$productID][BRANCH_MAIN] : array();
$productPlans[$productID][$branchID] += isset($plans[$productID][$branchID]) ? $plans[$productID][$branchID] : array();
if($branch != BRANCH_MAIN) $productPlans[$productID] = isset($plans[$productID][BRANCH_MAIN]) ? $plans[$productID][BRANCH_MAIN] : array();
$productPlans[$productID] += isset($plans[$productID][$branchID]) ? $plans[$productID][$branchID] : array();
if(!empty($projectStories[$productID][$branchID]) or !empty($projectBranches[$productID][$branchID]))
{
@@ -773,7 +766,6 @@ class project extends control
$this->view->multiBranchProducts = $this->loadModel('product')->getMultiBranchPairs();
$this->view->productPlans = array_filter($productPlans);
$this->view->linkedProducts = $linkedProducts;
$this->view->linkedBranches = $linkedBranches;
$this->view->branches = $branches;
$this->view->executions = $this->execution->getPairs($projectID);
$this->view->unmodifiableProducts = $unmodifiableProducts;
@@ -1286,7 +1278,6 @@ class project extends control
$this->view->branchOption = $branchOption;
$this->view->branchTagOption = $branchTagOption;
$this->view->executions = $executions;
$this->view->plans = $this->loadModel('productplan')->getPairs($productID ? $productID : array_keys($products));
$this->view->stories = $storyList;
$this->view->tasks = $taskList;
$this->view->projectPairs = $this->project->getPairsByProgram();
+8 -10
View File
@@ -15,16 +15,14 @@
#budget {border-right: 0px;}
#budgetUnit {border-left: 0px;}
#projectName {display: inline-block; width: 32%;}
#productsBox a {border-radius: 2px !important;}
#productsBox .col-sm-4 {padding-right: 5px;}
#productsBox .required:after {right: -9.5px !important;}
#productsBox .addProduct {padding-left: 0px !important; padding-right: 10px;}
#productsBox .addProduct.required:after {right: -1px !important;}
#productsBox .input-group-addon > div {display: inline-block !important;}
#productsBox .required + .text-danger.help-text {position: relative; left: 10px;}
#productsBox > #productNameLabel {padding-top: 8px;}
#plansBox .col-sm-4 {float: none; display: inline-block; padding-right: 5px;}
.productsBox a {border-radius: 2px !important;}
.productsBox .required:after {right: -9.5px !important;}
.productsBox .addProduct {padding-left: 0px !important;}
.productsBox .addProduct.required:after {right: -1px !important;}
.productsBox .input-group-addon > div {display: inline-block !important;}
.productsBox .required + .text-danger.help-text {position: relative; left: 10px;}
.productsBox > #productNameLabel {padding-top: 8px;}
.futureBox {vertical-align: top !important; padding-top: 13px !important;}
#projectType {padding-top: 7px !important;}
.division .icon-help {margin-left: 15px}
.has-info {border-color: #0c64eb!important;}
.has-info {border-color: #0c64eb!important;}
-1
View File
@@ -5,5 +5,4 @@
#dateRange, .futureBox {vertical-align: top !important; padding-top: 13px !important;}
#endList {vertical-align: top; padding-top: 13px;}
#linkPlan {padding-top: 25px !important;}
#productsBox .row .col-sm-4.required::after {right: -5px;}
#projectType {padding-top: 7px !important;}
-128
View File
@@ -138,134 +138,6 @@ function computeEndDate(delta)
$('#days').trigger('mousedown');
}
/**
* Load branches.
*
* @param object product
* @access public
* @return void
*/
function loadBranches(product)
{
/* When selecting a product, delete a plan that is empty by default. */
$("#planDefault").remove();
var chosenProducts = [];
$("#productsBox select[name^='products']").each(function()
{
var $product = $(product);
var productID = $(this).val();
if(productID > 0 && chosenProducts.indexOf(productID) == -1) chosenProducts.push(productID);
if($product.val() != 0 && $product.val() == $(this).val() && $product.attr('id') != $(this).attr('id') && !multiBranchProducts[$product.val()])
{
bootbox.alert(errorSameProducts);
$product.val(0);
$product.trigger("chosen:updated");
return false;
}
});
chosenProducts.length > 1 ? $('.division').removeClass('hide') : $('.division').addClass('hide');
if($('#productsBox .input-group:last select:first').val() != 0)
{
var length = $('#productsBox .row .input-group').size();
$('#productsBox .row').append('<div class="col-sm-4">' + $('#productsBox .col-sm-4:last').html() + '</div>');
if($('#productsBox .input-group:last select').size() >= 2) $('#productsBox .input-group:last select:last').remove();
$('#productsBox .input-group:last .chosen-container').remove();
$('#productsBox .input-group:last select:first').attr('name', 'products[' + length + ']').attr('id', 'products' + length);
$('#productsBox .input-group:last .chosen').chosen();
adjustProductBoxMargin();
}
var $inputgroup = $(product).closest('.input-group');
if($inputgroup.find('select').size() >= 2) $inputgroup.removeClass('has-branch').find('select:last').remove();
if($inputgroup.find('.chosen-container').size() >= 2) $inputgroup.find('.chosen-container:last').remove();
var index = $inputgroup.find('select:first').attr('id').replace('products' , '');
var oldBranch = $(product).attr('data-branch') !== undefined ? $(product).attr('data-branch') : 0;
$.get(createLink('branch', 'ajaxGetBranches', "productID=" + $(product).val() + "&oldBranch=" + oldBranch + "&param=active"), function(data)
{
if(data)
{
$inputgroup.addClass('has-branch').append(data);
$inputgroup.find('select:last').attr('name', 'branch[' + index + ']').attr('id', 'branch' + index).attr('onchange', "loadPlans('#products" + index + "', this.value)").chosen();
$inputgroup.find('select:last').each(disableSelectedBranch);
disableSelectedProduct();
}
var branchID = $('#branch' + index).val();
loadPlans(product, branchID);
});
if(!multiBranchProducts[$(product).val()]) disableSelectedProduct();
$("input[name='products[" + index + "]']").remove();
$("input[name='branch[" + index + "]']").remove();
}
function loadPlans(product, branchID)
{
if($('#plansBox').size() == 0) return false;
var productID = $(product).val();
var branchID = typeof(branchID) == 'undefined' ? 0 : branchID;
var planID = $(product).attr('data-plan') !== undefined ? $(product).attr('data-plan') : 0;
var index = $(product).attr('id').replace('products', '');
$("input[name='products[" + index + "]']").remove();
$("input[name='branch[" + index + "]']").remove();
$.get(createLink('product', 'ajaxGetPlans', "productID=" + productID + '&branch=0,' + branchID + '&planID=' + planID + '&fieldID&needCreate=&expired=unexpired,noclosed&param=skipParent,multiple'), function(data)
{
if(data)
{
if($("div#plan" + index).size() == 0) $("#plansBox .row").append('<div class="col-sm-4" id="plan' + index + '"></div>');
$("div#plan" + index).html(data).find('select').attr('name', 'plans[' + productID + ']' + '[' + branchID + '][]').attr('id', 'plans' + productID).chosen();
adjustPlanBoxMargin();
}
});
}
/**
* Adjust the layout of product selection.
*
* @access public
* @return void
*/
function adjustProductBoxMargin()
{
var productRows = Math.ceil($('#productsBox > .row > .col-sm-4').length / 3);
if(productRows > 1)
{
for(i = 1; i <= productRows - 1; i++)
{
$('#productsBox .col-sm-4:lt(' + (i * 3) + ')').css('margin-bottom', '10px');
}
}
}
/**
* Adjust the layout of the plan selection.
*
* @access public
* @return void
*/
function adjustPlanBoxMargin()
{
var planRows = Math.ceil($('#plansBox > .row > .col-sm-4').length / 3);
if(planRows > 1)
{
for(j = 1; j <= planRows - 1; j++)
{
$('#plansBox .col-sm-4:lt(' + (j * 3) + ')').css('margin-bottom', '10px');
}
}
}
/**
* Initialization operation.
*
+90 -94
View File
@@ -11,7 +11,7 @@ $(function()
if(hasProduct == 1)
{
var chosenProducts = 0;
$("#productsBox select[name^='products']").each(function()
$(".productsBox select[name^='products']").each(function()
{
if($(this).val() > 0) chosenProducts ++;
});
@@ -29,16 +29,16 @@ $(function()
setAclList($("#parent").val());
if(typeof(currentPlanID) == 'undefined')
if(typeof(currentPlanID) == 'undefined' && copyProjectID == 0)
{
$('#productsBox select[id^="products"]').each(function()
$('.productsBox select[id^="products"]').each(function()
{
var branchID = 0;
if($(this).closest('.input-group').find('select[id^="branch"]').size() > 0)
var branch = 0;
if($(this).closest('.table-row').find('select[name^="branch"]').size() > 0)
{
var branchID = $(this).closest('.input-group').find('select[id^="branch"]').val();
var branch = $(this).closest('.table-row').find('select[name^="branch"]');
}
loadPlans($(this), branchID);
loadPlans($(this), branch);
});
}
@@ -63,7 +63,7 @@ $(function()
$('#daysLabelInfo').remove();
/* Determine whether the products of the same branch are linked. */
$("#productsBox select[name^='products']").each(function()
$(".productsBox select[name^='products']").each(function()
{
var productID = $(this).val();
if(typeof(products[productID]) == 'undefined') products[productID] = new Array();
@@ -98,7 +98,6 @@ $(function()
}
/* Init for copy execution. */
$("select[id^=branch]").each(disableSelectedBranch);
disableSelectedProduct();
/* Check the all products and branches control when uncheck the product. */
@@ -106,14 +105,10 @@ $(function()
{
if($(this).val() == 0)
{
$("select[id^='branch']").each(disableSelectedBranch);
disableSelectedProduct();
}
});
$(document).on('change', "select[id^='branch']", disableSelectedBranch);
if(copyProjectID > 0 && copyType != 'previous')
{
$('#name').addClass('has-info')
@@ -167,7 +162,7 @@ function setParentProgram(parentProgram)
var productSelectHtml = data.allProducts;
var planSelectHtml = data.plans;
$('#productsBox .row .col-sm-4 .input-group').each(function(index)
$('.productsBox .row .col-sm-4 .input-group').each(function(index)
{
var selectedProduct = $(this).find('[name^=products]').val();
var selectedBranch = $(this).find('[name^=branch]').val();
@@ -177,7 +172,7 @@ function setParentProgram(parentProgram)
$(this).find('[name^=products]').attr('name', 'products[' + index + ']').attr('id', 'products' + index).attr('data-branch', selectedBranch).attr('data-plan', selectedPlan);
$(this).find('[name^=products]').val(selectedProduct).chosen().change();
});
$('#productsBox .row .col-sm-4:last').find('.input-group').append($('#productsBox .addProduct .input-group:first .input-group-addon').prop('outerHTML'));
$('.productsBox .row .col-sm-4:last').find('.input-group').append($('.productsBox .addProduct .input-group:first .input-group-addon').prop('outerHTML'));
}
if(parentProgram != 0)
@@ -198,13 +193,13 @@ function setParentProgram(parentProgram)
if(parentProgram != '0')
{
$('#productsBox .addProduct .input-group:first').addClass('required');
$('#productsBox .row .input-group:first').addClass('required');
$('.productsBox .addProduct .input-group:first').addClass('required');
$('.productsBox .row .input-group:first').addClass('required');
}
else
{
$('#productsBox .addProduct .input-group').removeClass('required');
$('#productsBox .row .input-group').removeClass('required');
$('.productsBox .addProduct .input-group').removeClass('required');
$('.productsBox .row .input-group').removeClass('required');
}
}
@@ -232,35 +227,37 @@ function addNewProduct(obj)
if($(obj).attr('checked'))
{
/* Hide product and plan dropdown controls. */
$('#productsBox .row .col-sm-4').addClass('hidden');
$('#productsBox .row .col-sm-4 .input-group').find('select').attr('disabled', true).trigger("chosen:updated");
$('#plansBox').closest('tr').addClass('hidden');
$('#plansBox .col-sm-4').find('select').attr('disabled', true).trigger("chosen:updated");
$('.newLine').addClass('hidden');
$('.productsBox .row').addClass('hidden');
$('.productsBox .row .input-group').find('select').attr('disabled', true).trigger("chosen:updated");
$('.division').addClass('hide');
/* Displays the input box for creating a product. */
$(obj).closest('td').attr('colspan', 1);
$("[name^='newProduct']").prop('checked', true);
$('#productName').removeAttr('disabled', true);
$('#productsBox .addProduct').removeClass('hidden');
$('.productsBox .addProduct').removeClass('hidden');
$('#productTitle').html(productName);
}
else
{
/* Show product and product dropdown controls. */
$('#productsBox .row .col-sm-4').removeClass('hidden');
$('#productsBox .row .col-sm-4 .input-group').find('select').removeAttr('disabled').trigger("chosen:updated");
$('#plansBox').closest('tr').removeClass('hidden');
$('#plansBox .col-sm-4').find('select').removeAttr('disabled', true).trigger("chosen:updated");
$('.newLine').removeClass('hidden');
$('.productsBox .row').removeClass('hidden');
$('.productsBox .row .input-group').find('select').removeAttr('disabled').trigger("chosen:updated");
if($('#plansBox .col-sm-4').find('select').length > 1) $('.division').removeClass('hide');
/* Hide the input box for creating a product. */
$(obj).closest('td').attr('colspan', 3);
$("[name^='newProduct']").prop('checked', false);
$('#productName').attr('disabled', true);
$('#productsBox .addProduct').addClass('hidden');
$('.productsBox .addProduct').addClass('hidden');
$('#productTitle').html(manageProducts);
$('#productTitle').html(manageProductPlan);
}
$('#productsBox div + .text-danger.help-text').remove();
$('.productsBox div + .text-danger.help-text').remove();
disableSelectedProduct();
}
/**
@@ -295,7 +292,7 @@ function loadBranches(product)
$("#planDefault").remove();
var chosenProducts = [];
$("#productsBox select[name^='products']").each(function()
$(".productsBox select[name^='products']").each(function()
{
var $product = $(product);
var productID = $(this).val();
@@ -311,111 +308,110 @@ function loadBranches(product)
(chosenProducts.length > 1 && model == 'waterfall') ? $('.division').removeClass('hide') : $('.division').addClass('hide');
if($('#productsBox .row .input-group:last select:first').val() != 0)
var $tableRow = $(product).closest('.table-row');
var index = $tableRow.find('select:first').attr('id').replace('products' , '');
var oldBranch = $(product).attr('data-branch') !== undefined ? $(product).attr('data-branch') : 0;
if($(product).val() != 0)
{
var length = $('#productsBox .row .input-group').size();
var $html = $('#productsBox .row .col-sm-4:last').html().replace('required', '');
$('#productsBox .row .col-sm-4:last').find('.input-group-addon').remove();
$('#productsBox .row').append('<div class="col-sm-4">' + $html + '</div>');
if($('#productsBox .row .input-group:last select').size() >= 2) $('#productsBox .row .input-group:last select:last').remove();
$('#productsBox .row .input-group:last .chosen-container').remove();
$('#productsBox .row .input-group:last select:first').attr('name', 'products[' + length + ']').attr('id', 'products' + length);
$('#productsBox .row .input-group:last .chosen').chosen();
$('[data-toggle="popover"]').popover();
adjustProductBoxMargin();
$(product).closest('tr').find('.newProduct').addClass('hidden')
}
else
{
$(product).closest('tr').find('.newProduct').removeClass('hidden')
}
var $inputgroup = $(product).closest('.input-group');
if($inputgroup.find('select').size() >= 2) $inputgroup.removeClass('has-branch').find('select:last').remove();
if($inputgroup.find('.chosen-container').size() >= 2) $inputgroup.find('.chosen-container:last').remove();
if(!multiBranchProducts[$(product).val()])
{
$tableRow.find('.table-col:last select').val('').trigger('chosen:updated');
$tableRow.find('.table-col:last').addClass('hidden');
}
var index = $inputgroup.find('select:first').attr('id').replace('products' , '');
var oldBranch = $(product).attr('data-branch') !== undefined ? $(product).attr('data-branch') : 0;
$.get(createLink('branch', 'ajaxGetBranches', "productID=" + $(product).val() + "&oldBranch=" + oldBranch + "&param=active"), function(data)
{
if(data)
{
$inputgroup.addClass('has-branch').append(data);
$inputgroup.find('select:last').attr('name', 'branch[' + index + ']').attr('id', 'branch' + index).attr('onchange', "loadPlans('#products" + index + "', this.value)").chosen();
$tableRow.find("select[name^='branch']").replaceWith(data);
$tableRow.find('.table-col:last .chosen-container').remove();
$tableRow.find('.table-col:last').removeClass('hidden');
$tableRow.find("select[name^='branch']").attr('multiple', '').attr('name', 'branch[' + index + '][]').attr('id', 'branch' + index).attr('onchange', "loadPlans('#products" + index + "', this)").chosen();
$inputgroup.find('select:last').each(disableSelectedBranch);
disableSelectedProduct();
}
var branchID = $('#branch' + index).val();
loadPlans(product, branchID);
var branch = $('#branch' + index);
loadPlans(product, branch);
});
if(!multiBranchProducts[$(product).val()]) disableSelectedProduct();
}
/**
* Load plans.
*
* @param obj $product
* @param int $branchID
* @param obj $branchID
* @access public
* @return void
*/
function loadPlans(product, branchID)
function loadPlans(product, branch)
{
if($('#plansBox').size() == 0) return false;
var productID = $(product).val();
var branchID = typeof(branchID) == 'undefined' ? 0 : branchID;
var branchID = $(branch).val() == null ? 0 : '0,' + $(branch).val();
var planID = $(product).attr('data-plan') !== undefined ? $(product).attr('data-plan') : 0;
var index = $(product).attr('id').replace('products', '');
$.get(createLink('product', 'ajaxGetPlans', "productID=" + productID + '&branch=0,' + branchID + '&planID=' + planID + '&fieldID&needCreate=&expired=unexpired,noclosed&param=skipParent,multiple'), function(data)
$.get(createLink('product', 'ajaxGetPlans', "productID=" + productID + '&branch=' + branchID + '&planID=' + planID + '&fieldID&needCreate=&expired=unexpired,noclosed&param=skipParent,multiple'), function(data)
{
if(data)
{
if($("div#plan" + index).size() == 0) $("#plansBox .row").append('<div class="col-sm-4" id="plan' + index + '"></div>');
$("div#plan" + index).html(data).find('select').attr('name', 'plans[' + productID + '][' + branchID + '][]').attr('id', 'plans' + productID).chosen();
adjustPlanBoxMargin();
$("div#plan" + index).find("select[name^='plans']").replaceWith(data);
$("div#plan" + index).find('.chosen-container').remove();
$("div#plan" + index).find('select').attr('name', 'plans[' + productID + ']' + '[]').attr('id', 'plans' + productID).chosen();
}
});
}
/**
* Adjust product box margin.
*
* Add new line for link product.
*
* @param obj $obj
* @access public
* @return void
*/
function adjustProductBoxMargin()
function addNewLine(obj)
{
var productRows = Math.ceil($('#productsBox > .row > .col-sm-4').length / 3);
if(productRows > 1)
var newLine = $(obj).closest('tr').clone();
var index = 0;
$(".productsBox select[name^='products']").each(function()
{
for(i = 1; i <= productRows - 1; i++)
{
$('#productsBox .row .col-sm-4:lt(' + (i * 3) + ')').css('margin-bottom', '10px');
$('#productsBox .row .col-sm-4').eq(i * 3).css('padding-right', '6px');
}
}
var id = $(this).attr('id').replace('products' , '');
id = parseInt(id);
id ++;
index = id > index ? id : index;
})
newLine.find('.newProduct').remove();
newLine.find('.addProduct').remove();
newLine.addClass('newLine');
newLine.find('th').html('');
newLine.find('.removeLine').css('visibility', 'visible');
newLine.find('.chosen-container').remove();
newLine.find('.productsBox .table-col:last').addClass('hidden');
newLine.find("select[name^='products']").attr('name', 'products[' + index + ']').attr('id', 'products' + index).val('').chosen();
newLine.find("select[name^='plans']").attr('name', 'plans[' + index + '][' + 0 + '][]').chosen();
newLine.find("div[id^='plan']").attr('id', 'plan' + index);
$(obj).closest('tr').after(newLine);
var product = newLine.find("select[name^='products']");
var branch = newLine.find("select[name^='branch']");
loadPlans(product, branch);
disableSelectedProduct();
}
/**
* Adjust plan box margin.
*
* @access public
* @return void
*/
function adjustPlanBoxMargin()
function removeLine(obj)
{
var planRows = Math.ceil($('#plansBox > .row > .col-sm-4').length / 3);
if(planRows > 1)
{
for(j = 1; j <= planRows - 1; j++)
{
$('#plansBox .col-sm-4:lt(' + (j * 3) + ')').css('margin-bottom', '10px');
$('#plansBox .col-sm-4').eq(j * 3).css('padding-right', '6px');
}
}
$(obj).closest('tr').remove();
disableSelectedProduct();
}
/**
+124 -3
View File
@@ -32,9 +32,6 @@ $(function()
});
});
adjustProductBoxMargin();
adjustPlanBoxMargin();
$(document).on('change', '[name*=products]', function()
{
var current = $(this).val();
@@ -173,3 +170,127 @@ function setAclList(programID)
$('.aclBox').html($('#projectAcl').html());
}
}
/**
* Load branches.
*
* @param int $product
* @access public
* @return void
*/
function loadBranches(product)
{
/* When selecting a product, delete a plan that is empty by default. */
$("#planDefault").remove();
var chosenProducts = [];
$(".productsBox select[name^='products']").each(function()
{
var $product = $(product);
var productID = $(this).val();
if(productID > 0 && chosenProducts.indexOf(productID) == -1) chosenProducts.push(productID);
if($product.val() != 0 && $product.val() == $(this).val() && $product.attr('id') != $(this).attr('id') && !multiBranchProducts[$product.val()])
{
bootbox.alert(errorSameProducts);
$product.val(0);
$product.trigger("chosen:updated");
return false;
}
});
(chosenProducts.length > 1 && model == 'waterfall') ? $('.division').removeClass('hide') : $('.division').addClass('hide');
var $tableRow = $(product).closest('.table-row');
var index = $tableRow.find('select:first').attr('id').replace('products' , '');
var oldBranch = $(product).attr('data-branch') !== undefined ? $(product).attr('data-branch') : 0;
if(!multiBranchProducts[$(product).val()])
{
$tableRow.find('.table-col:last select').val('').trigger('chosen:updated');
$tableRow.find('.table-col:last').addClass('hidden');
}
$.get(createLink('branch', 'ajaxGetBranches', "productID=" + $(product).val() + "&oldBranch=" + oldBranch + "&param=active"), function(data)
{
if(data)
{
$tableRow.find("select[name^='branch']").replaceWith(data);
$tableRow.find('.table-col:last .chosen-container').remove();
$tableRow.find('.table-col:last').removeClass('hidden');
$tableRow.find("select[name^='branch']").attr('multiple', '').attr('name', 'branch[' + index + '][]').attr('id', 'branch' + index).attr('onchange', "loadPlans('#products" + index + "', this)").chosen();
disableSelectedProduct();
}
var branch = $('#branch' + index);
loadPlans(product, branch);
});
}
/**
* Load plans.
*
* @param obj $product
* @param obj $branchID
* @access public
* @return void
*/
function loadPlans(product, branch)
{
var productID = $(product).val();
var branchID = $(branch).val() == null ? 0 : '0,' + $(branch).val();
var planID = $(product).attr('data-plan') !== undefined ? $(product).attr('data-plan') : 0;
var index = $(product).attr('id').replace('products', '');
$.get(createLink('product', 'ajaxGetPlans', "productID=" + productID + '&branch=' + branchID + '&planID=' + planID + '&fieldID&needCreate=&expired=unexpired,noclosed&param=skipParent,multiple'), function(data)
{
if(data)
{
$("div#plan" + index).find("select[name^='plans']").replaceWith(data);
$("div#plan" + index).find('.chosen-container').remove();
$("div#plan" + index).find('select').attr('name', 'plans[' + productID + ']' + '[]').attr('id', 'plans' + productID).chosen();
}
});
}
/**
* Add new line for link product.
*
* @param obj $obj
* @access public
* @return void
*/
function addNewLine(obj)
{
var newLine = $(obj).closest('tr').clone();
var index = 0;
$(".productsBox select[name^='products']").each(function()
{
var id = $(this).attr('id').replace('products' , '');
id = parseInt(id);
id ++;
index = id > index ? id : index;
})
newLine.addClass('newLine');
newLine.find('th').html('');
newLine.find('.removeLine').css('visibility', 'visible');
newLine.find('.chosen-container').remove();
newLine.find('.productsBox .table-col:last').addClass('hidden');
newLine.find("select[name^='products']").attr('name', 'products[' + index + ']').attr('id', 'products' + index).val('').chosen();
newLine.find("select[name^='plans']").attr('name', 'plans[' + index + '][' + 0 + '][]').chosen();
newLine.find("div[id^='plan']").attr('id', 'plan' + index);
$(obj).closest('tr').after(newLine);
var product = newLine.find("select[name^='products']");
var branch = newLine.find("select[name^='branch']");
loadPlans(product, branch);
disableSelectedProduct();
}
function removeLine(obj)
{
$(obj).closest('tr').remove();
}
+2
View File
@@ -42,6 +42,7 @@ $lang->project->addWhitelist = 'Project Add Whitelist';
$lang->project->unbindWhitelist = 'Project Remove Whitelist';
$lang->project->manageProducts = 'Manage Products';
$lang->project->manageOtherProducts = 'Manage Other Products';
$lang->project->manageProductPlan = 'Manage Products And Plans';
$lang->project->copyTitle = 'Please select an project to copy';
$lang->project->errorSameProducts = 'Project cannot be associated with multiple identical products.';
$lang->project->errorSameBranches = 'Project cannot be associated with multiple identical branches.';
@@ -338,6 +339,7 @@ $lang->project->scrum = 'Scrum';
$lang->project->waterfall = 'CMMI';
$lang->project->cannotCreateChild = 'It is not empty, so you cannot add a child. You can add a parent for it, and then create a child.';
$lang->project->emptyPM = 'No manager';
$lang->project->emptyBranch = 'Branch can not be empty!';
$lang->project->cannotChangeToCat = "It is not empty, so you cannot change it to a parent.";
$lang->project->cannotCancelCat = "It has child projects, so you cannot unmark the parent.";
$lang->project->parentBeginEnd = "Parent begin&end date: %s ~ %s";
+2
View File
@@ -42,6 +42,7 @@ $lang->project->addWhitelist = 'Project Add Whitelist';
$lang->project->unbindWhitelist = 'Project Remove Whitelist';
$lang->project->manageProducts = 'Manage Products';
$lang->project->manageOtherProducts = 'Manage Other Products';
$lang->project->manageProductPlan = 'Manage Products And Plans';
$lang->project->copyTitle = 'Please select an project to copy';
$lang->project->errorSameProducts = 'Project cannot be associated with multiple identical products.';
$lang->project->errorSameBranches = 'Project cannot be associated with multiple identical branches.';
@@ -338,6 +339,7 @@ $lang->project->scrum = 'Scrum';
$lang->project->waterfall = 'Waterfall';
$lang->project->cannotCreateChild = 'It is not empty, so you cannot add a child. You can add a parent for it, and then create a child.';
$lang->project->emptyPM = 'No manager';
$lang->project->emptyBranch = 'Branch can not be empty!';
$lang->project->cannotChangeToCat = "It is not empty, so you cannot change it to a parent.";
$lang->project->cannotCancelCat = "It has child projects, so you cannot unmark the parent.";
$lang->project->parentBeginEnd = "Parent begin&end date: %s ~ %s";
+2
View File
@@ -42,6 +42,7 @@ $lang->project->addWhitelist = 'Project Add Whitelist';
$lang->project->unbindWhitelist = 'Project Remove Whitelist';
$lang->project->manageProducts = 'Manage Products';
$lang->project->manageOtherProducts = 'Manage Other Products';
$lang->project->manageProductPlan = 'Manage Products And Plans';
$lang->project->copyTitle = 'Please select an project to copy';
$lang->project->errorSameProducts = 'Project cannot be associated with multiple identical products.';
$lang->project->errorSameBranches = 'Project cannot be associated with multiple identical branches.';
@@ -338,6 +339,7 @@ $lang->project->scrum = 'Scrum';
$lang->project->waterfall = 'CMMI';
$lang->project->cannotCreateChild = 'It is not empty, so you cannot add a child. You can add a parent for it, and then create a child.';
$lang->project->emptyPM = 'No manager';
$lang->project->emptyBranch = 'Branch can not be empty!';
$lang->project->cannotChangeToCat = "It is not empty, so you cannot change it to a parent.";
$lang->project->cannotCancelCat = "It has child projects, so you cannot unmark the parent.";
$lang->project->parentBeginEnd = "Parent begin&end date: %s ~ %s";
+2
View File
@@ -42,6 +42,7 @@ $lang->project->addWhitelist = 'Project Add Whitelist';
$lang->project->unbindWhitelist = 'Project Remove Whitelist';
$lang->project->manageProducts = 'Manage Products';
$lang->project->manageOtherProducts = 'Manage Other Products';
$lang->project->manageProductPlan = 'Manage Products And Plans';
$lang->project->copyTitle = 'Please select an project to copy';
$lang->project->errorSameProducts = 'Project cannot be associated with multiple identical products.';
$lang->project->errorSameBranches = 'Project cannot be associated with multiple identical branches.';
@@ -335,6 +336,7 @@ $lang->project->scrum = 'Scrum';
$lang->project->waterfall = 'Waterfall';
$lang->project->cannotCreateChild = 'It is not empty, so you cannot add a child. You can add a parent for it, and then create a child.';
$lang->project->emptyPM = 'No manager';
$lang->project->emptyBranch = 'Branch can not be empty!';
$lang->project->cannotChangeToCat = "It is not empty, so you cannot change it to a parent.";
$lang->project->cannotCancelCat = "It has child projects, so you cannot unmark the parent.";
$lang->project->parentBeginEnd = "Parent begin&end date: %s ~ %s";
+4 -2
View File
@@ -42,6 +42,7 @@ $lang->project->addWhitelist = '项目添加白名单';
$lang->project->unbindWhitelist = '项目删除白名单';
$lang->project->manageProducts = '关联产品';
$lang->project->manageOtherProducts = '关联其他产品';
$lang->project->manageProductPlan = '关联产品和计划';
$lang->project->copyTitle = '请选择要复制的项目';
$lang->project->errorSameProducts = '项目不能关联多个相同的产品。';
$lang->project->errorSameBranches = '项目不能关联多个相同的分支。';
@@ -233,7 +234,7 @@ $lang->project->hundredMillion = '亿';
$lang->project->unitList['CNY'] = '人民币';
$lang->project->unitList['USD'] = '美元';
$lang->project->unitList['HKD'] = '港元';
$lang->project->unitList['NTD'] = '台元';
$lang->project->unitList['NTD'] = '台币';
$lang->project->unitList['EUR'] = '欧元';
$lang->project->unitList['DEM'] = '马克';
$lang->project->unitList['CHF'] = '瑞士法郎';
@@ -338,6 +339,7 @@ $lang->project->scrum = 'Scrum';
$lang->project->waterfall = '瀑布';
$lang->project->cannotCreateChild = '该项目已经有实际的内容,无法直接添加子项目。您可以为当前项目创建一个父项目,然后在新的父项目下面添加子项目。';
$lang->project->emptyPM = '暂无';
$lang->project->emptyBranch = '分支不能为空!';
$lang->project->cannotChangeToCat = "该项目已经有实际的内容,无法修改为父项目";
$lang->project->cannotCancelCat = "该项目下已经有子项目,无法取消父项目标记";
$lang->project->parentBeginEnd = "父项目起止时间:%s ~ %s";
@@ -379,4 +381,4 @@ $lang->project->copyProject = new stdClass();
$lang->project->copyProject->nameTips = '『项目名称』不可重复需要修改。';
$lang->project->copyProject->codeTips = '『项目代号』不可重复需要修改。';
$lang->project->copyProject->endTips = '『计划完成』不能为空。';
$lang->project->copyProject->daysTips = '『可用工作日』应当是数字。';
$lang->project->copyProject->daysTips = '『可用工作日』应当是数字。';
+1 -1
View File
@@ -190,7 +190,7 @@ $lang->project->tenThousand = '萬';
$lang->project->unitList['CNY'] = '人民幣';
$lang->project->unitList['USD'] = '美元';
$lang->project->unitList['HKD'] = '港元';
$lang->project->unitList['NTD'] = '台元';
$lang->project->unitList['NTD'] = '台幣';
$lang->project->unitList['EUR'] = '歐元';
$lang->project->unitList['DEM'] = '馬克';
$lang->project->unitList['CHF'] = '瑞士法郎';
+50 -15
View File
@@ -1243,6 +1243,20 @@ class projectModel extends model
}
}
if($_POST['products'])
{
$topProgramID = $this->loadModel('program')->getTopByID($project->parent);
$multipleProducts = $this->loadModel('product')->getMultiBranchPairs($topProgramID);
foreach($_POST['products'] as $index => $productID)
{
if(isset($multipleProducts[$productID]) and empty($_POST['branch'][$index]))
{
dao::$errors[] = $this->lang->project->emptyBranch;
return false;
}
}
}
$program = new stdClass();
if($project->parent)
{
@@ -1485,6 +1499,7 @@ class projectModel extends model
->setDefault('team', $this->post->name)
->setDefault('lastEditedBy', $this->app->user->account)
->setDefault('lastEditedDate', helper::now())
->setDefault('parent', 0)
->setIF($this->post->delta == 999, 'end', LONG_TIME)
->setIF($this->post->delta == 999, 'days', 0)
->setIF($this->post->begin == '0000-00-00', 'begin', '')
@@ -1508,12 +1523,27 @@ class projectModel extends model
if(dao::isError()) return false;
}
if($_POST['products'])
{
$topProgramID = $this->loadModel('program')->getTopByID($project->parent);
$multipleProducts = $this->loadModel('product')->getMultiBranchPairs($topProgramID);
foreach($_POST['products'] as $index => $productID)
{
if(isset($multipleProducts[$productID]) and empty($_POST['branch'][$index]))
{
dao::$errors[] = $this->lang->project->emptyBranch;
return false;
}
}
}
/* Judge products not empty. */
$linkedProductsCount = 0;
foreach($_POST['products'] as $product)
{
if(!empty($product)) $linkedProductsCount++;
}
if(empty($linkedProductsCount))
{
dao::$errors[] = $this->lang->project->errorNoProducts;
@@ -2403,24 +2433,29 @@ class projectModel extends model
$oldPlan = 0;
$branch = isset($branches[$i]) ? $branches[$i] : 0;
if(isset($existedProducts[$productID][$branch])) continue;
if(!is_array($branch)) $branch = array($branch => $branch);
if(isset($oldProjectProducts[$productID][$branch]))
foreach($branch as $branchID)
{
$oldProjectProduct = $oldProjectProducts[$productID][$branch];
if($this->app->rawMethod != 'edit') $oldPlan = $oldProjectProduct->plan;
if(isset($existedProducts[$productID][$branchID])) continue;
if(isset($oldProjectProducts[$productID][$branchID]))
{
$oldProjectProduct = $oldProjectProducts[$productID][$branchID];
if($this->app->rawMethod != 'edit') $oldPlan = $oldProjectProduct->plan;
}
$data = new stdclass();
$data->project = $projectID;
$data->product = $productID;
$data->branch = $branchID;
$data->plan = isset($plans[$productID]) ? implode(',', $plans[$productID]) : $oldPlan;
$data->plan = trim($data->plan, ',');
$data->plan = empty($data->plan) ? 0 : ",$data->plan,";
$this->dao->insert(TABLE_PROJECTPRODUCT)->data($data)->exec();
$existedProducts[$productID][$branchID] = true;
}
$data = new stdclass();
$data->project = $projectID;
$data->product = $productID;
$data->branch = $branch;
$data->plan = isset($plans[$productID][$branch]) ? implode(',', $plans[$productID][$branch]) : $oldPlan;
$data->plan = trim($data->plan, ',');
$data->plan = empty($data->plan) ? 0 : ",$data->plan,";
$this->dao->insert(TABLE_PROJECTPRODUCT)->data($data)->exec();
$existedProducts[$productID][$branch] = true;
}
/* Delete the execution linked products that is not linked with the execution. */
+72 -45
View File
@@ -26,7 +26,7 @@
<?php js::set('selectedProductID', $productID);?>
<?php js::set('selectedBranchID', $branchID);?>
<?php js::set('productName', $lang->product->name);?>
<?php js::set('manageProducts', $lang->project->manageProducts);?>
<?php js::set('manageProductPlan', $lang->project->manageProductPlan);?>
<?php js::set('budgetOverrun', $lang->project->budgetOverrun);?>
<?php js::set('currencySymbol', $lang->project->currencySymbol)?>
<?php js::set('parentBudget', $lang->project->parentBudget);?>
@@ -143,64 +143,91 @@
</div>
</td><td></td><td></td>
</tr>
<?php if($products):?>
<?php $i = 0;?>
<?php foreach($products as $product):?>
<tr>
<th id='productTitle'><?php echo $lang->project->manageProducts;?></th>
<td class='text-left' id='productsBox' colspan="3">
<th><?php if($i == 0) echo $lang->project->manageProductPlan;?></th>
<td class='text-left productsBox' colspan="3">
<div class='row'>
<?php $i = 0;?>
<?php foreach($products as $product):?>
<?php foreach($product->branches as $branch):?>
<div class="col-sm-4" style="margin-bottom: 10px; padding-right: 6px;">
<?php $hasBranch = $product->type != 'normal' and isset($branchGroups[$product->id]);?>
<div class="input-group<?php if($hasBranch) echo ' has-branch';?>">
<?php echo html::select("products[$i]", $allProducts, $product->id, "class='form-control chosen' onchange='loadBranches(this)' data-last='" . $product->id . "'");?>
<span class='input-group-addon fix-border'></span>
<?php if($hasBranch) echo html::select("branch[$i]", $branchGroups[$product->id], $branch, "class='form-control chosen' onchange=\"loadPlans('#products{$i}', this.value)\"");?>
<div class="col-sm-6">
<div class='table-row'>
<div class='table-col'>
<?php $hasBranch = $product->type != 'normal' and isset($branchGroups[$product->id]);?>
<div class='input-group <?php if($hasBranch) echo ' has-branch';?>'>
<span class='input-group-addon'><?php echo $lang->product->common;?></span>
<?php echo html::select("products[$i]", $allProducts, $product->id, "class='form-control chosen' onchange='loadBranches(this)' data-last='" . $product->id . "' data-type='" . $product->type . "'");?>
</div>
</div>
<div class='table-col <?php if(!$hasBranch) echo 'hidden';?>'>
<div class='input-group required'>
<span class='input-group-addon fix-border'><?php echo $lang->product->branchName['branch'];?></span>
<?php $branchIdList = join(',', $product->branches);?>
<?php echo html::select("branch[$i][]", isset($branchGroups[$product->id]) ? $branchGroups[$product->id] : array(), $branchIdList, "class='form-control chosen' multiple onchange=\"loadPlans('#products{$i}', this)\"");?>
</div>
</div>
</div>
</div>
<?php $i++;?>
<?php endforeach;?>
<?php endforeach;?>
<div class='col-sm-4 <?php if($programID) echo 'required';?>' style="padding-right: 6px;">
<div class='input-group'>
<?php echo html::select("products[$i]", $allProducts, '', "class='form-control chosen' onchange='loadBranches(this)'");?>
<?php if(common::hasPriv('product', 'create')):?>
<span class='input-group-addon'>
<?php echo html::checkBox('newProduct', $lang->project->addProduct, '', "onchange=addNewProduct(this);");?>
<div><icon class='icon icon-help' data-toggle='popover' data-trigger='focus hover' data-placement='left' data-tip-class='text-muted popover-sm' data-content="<?php echo $lang->project->productTip;?>"></icon></div>
</span>
<?php endif;?>
<div class="col-sm-6">
<div class='input-group' <?php echo "id='plan$i'";?>>
<span class='input-group-addon'><?php echo $lang->product->plan;?></span>
<?php echo html::select("plans[$product->id][]", isset($productPlans[$product->id]) ? $productPlans[$product->id] : array(), $product->plans, "class='form-control chosen' multiple");?>
<div class='input-group-btn'>
<a href='javascript:;' onclick='addNewLine(this)' class='btn btn-link addLine'><i class='icon-plus'></i></a>
<a href='javascript:;' onclick='removeLine(this)' class='btn btn-link removeLine' <?php if($i == 0) echo "style='visibility: hidden'";?>><i class='icon-close'></i></a>
</div>
</div>
</div>
</div>
<div class="col-sm-4 addProduct hidden <?php if($programID) echo 'required';?>">
<div class='input-group'>
<?php echo html::input('productName', '', "class='form-control'");?>
<span class='input-group-addon'><?php echo html::checkBox('newProduct', $lang->project->addProduct, '', "onchange=addNewProduct(this);");?></span>
</div>
</div>
</td>
</tr>
<?php $i ++;?>
<?php endforeach;?>
<?php else:?>
<tr>
<th id='linkPlan'><?php echo $lang->execution->linkPlan;?></th>
<td colspan="3" id="plansBox">
<th id='productTitle'><?php echo $lang->project->manageProductPlan;?></th>
<td class='text-left productsBox' colspan='3'>
<div class='row'>
<?php if($copyProjectID):?>
<?php $i = 0;?>
<?php foreach($products as $product):?>
<?php $productPlan = zget($productPlans, $product->id, array(0 => ''));?>
<?php foreach($product->branches as $branch):?>
<?php $plans = zget($productPlan, $branch, array(0 => ''));?>
<div class="col-sm-4" id="plan<?php echo $i;?>"><?php echo html::select('plans[' . $product->id . '][' . $branch . '][]', $plans, '', "class='form-control chosen' multiple");?></div>
<?php $i++;?>
<?php endforeach;?>
<?php endforeach;?>
<?php else:?>
<div class="col-sm-4" id="plan0" style="padding-right: 6px;"><?php echo html::select("plans[][][]", '', '', "class='form-control chosen' multiple");?></div>
<?php endif;?>
<div class="col-sm-6">
<div class='table-row'>
<div class='table-col'>
<div class='input-group'>
<span class='input-group-addon'><?php echo $lang->product->common;?></span>
<?php echo html::select("products[0]", $allProducts, '', "class='form-control chosen' onchange='loadBranches(this)'");?>
<?php if(common::hasPriv('product', 'create')):?>
<span class='input-group-addon newProduct'>
<?php echo html::checkBox('newProduct', $lang->project->addProduct, '', "onchange=addNewProduct(this);");?>
<div><icon class='icon icon-help' data-toggle='popover' data-trigger='focus hover' data-placement='left' data-tip-class='text-muted popover-sm' data-content="<?php echo $lang->project->productTip;?>"></icon></div>
</span>
<?php endif;?>
</div>
</div>
<div class='table-col hidden'>
<div class='input-group required'>
<span class='input-group-addon fix-border'><?php echo $lang->product->branchName['branch'];?></span>
<?php echo html::select("branch", '', '', "class='form-control chosen' multiple");?>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class='input-group' id='plan0'>
<span class='input-group-addon'><?php echo $lang->product->plan;?></span>
<?php echo html::select("plans[][]", '', '', "class='form-control chosen' multiple");?>
<div class='input-group-btn'>
<a href='javascript:;' onclick='addNewLine(this)' class='btn btn-link addLine'><i class='icon-plus'></i></a>
<a href='javascript:;' onclick='removeLine(this)' class='btn btn-link removeLine' style='visibility: hidden'><i class='icon-close'></i></a>
</div>
</div>
</div>
</div>
<div class="input-group addProduct hidden <?php if($programID) echo 'required';?>">
<?php echo html::input('productName', '', "class='form-control'");?>
<span class='input-group-addon'><?php echo html::checkBox('newProduct', $lang->project->addProduct, '', "onchange=addNewProduct(this);");?></span>
</div>
</td>
</tr>
<?php endif;?>
<?php if($model == 'waterfall'):?>
<tr class='hide division'>
<th><?php echo $lang->project->division;?></th>
+30 -34
View File
@@ -146,49 +146,45 @@
<td></td>
</tr>
<?php if($project->hasProduct and $this->config->vision != 'lite'):?>
<?php $i = 0;?>
<?php foreach($linkedProducts as $product):?>
<tr>
<th><?php echo $lang->project->manageProducts;?></th>
<td class='text-left' id='productsBox' colspan="3">
<th><?php if($i == 0) echo $lang->project->manageProductPlan;?></th>
<td class='text-left productsBox' colspan="3">
<div class='row'>
<?php $i = 0;?>
<?php foreach($linkedProducts as $product):?>
<?php $hasBranch = $product->type != 'normal' and isset($branchGroups[$product->id]);?>
<?php foreach($linkedBranches[$product->id] as $branchID => $branch):?>
<div class='col-sm-4' style="padding-right: 6px;">
<div class="input-group<?php if($hasBranch) echo ' has-branch';?>">
<?php echo html::select("products[$i]", $allProducts, $product->id, "class='form-control chosen' onchange='loadBranches(this)' data-last='" . $product->id . "' data-type='" . $product->type . "' data-lastBranch='" . $branchID . "'");?>
<span class='input-group-addon fix-border'></span>
<?php if($hasBranch) echo html::select("branch[$i]", $branchGroups[$product->id], $branchID, "class='form-control chosen' onchange=\"loadPlans('#products{$i}', this.value)\" data-last='" . $branchID . "'");?>
<div class="col-sm-6">
<div class='table-row'>
<div class='table-col'>
<?php $hasBranch = $product->type != 'normal' and isset($branchGroups[$product->id]);?>
<div class='input-group <?php if($hasBranch) echo ' has-branch';?>'>
<span class='input-group-addon'><?php echo $lang->product->common;?></span>
<?php echo html::select("products[$i]", $allProducts, $product->id, "class='form-control chosen' onchange='loadBranches(this)' data-last='" . $product->id . "' data-type='" . $product->type . "'");?>
</div>
</div>
<div class='table-col <?php if(!$hasBranch) echo 'hidden';?>'>
<div class='input-group required'>
<span class='input-group-addon fix-border'><?php echo $lang->product->branchName['branch'];?></span>
<?php $branchIdList = join(',', $product->branches);?>
<?php echo html::select("branch[$i][]", isset($branchGroups[$product->id]) ? $branchGroups[$product->id] : array(), $branchIdList, "class='form-control chosen' multiple onchange=\"loadPlans('#products{$i}', this)\"");?>
</div>
</div>
</div>
</div>
<?php $i++;?>
<?php endforeach;?>
<?php endforeach;?>
<div class='col-sm-4 <?php if($projectID) echo 'required';?>' style="padding-right: 6px;">
<div class='input-group'>
<?php echo html::select("products[$i]", $allProducts, '', "class='form-control chosen' onchange='loadBranches(this)'");?>
<span class='input-group-addon fix-border'></span>
<div class="col-sm-6">
<div class='input-group' <?php echo "id='plan$i'";?>>
<span class='input-group-addon'><?php echo $lang->product->plan;?></span>
<?php echo html::select("plans[$product->id][]", isset($productPlans[$product->id]) ? $productPlans[$product->id] : array(), $product->plans, "class='form-control chosen' multiple");?>
<div class='input-group-btn'>
<a href='javascript:;' onclick='addNewLine(this)' class='btn btn-link addLine'><i class='icon-plus'></i></a>
<a href='javascript:;' onclick='removeLine(this)' class='btn btn-link removeLine' <?php if($i == 0) echo "style='visibility: hidden'";?>><i class='icon-close'></i></a>
</div>
</div>
</div>
</div>
</td>
</tr>
<tr>
<th id='linkPlan'><?php echo $lang->execution->linkPlan;?></th>
<td id="plansBox" colspan="3">
<div class='row'>
<?php $i = 0;?>
<?php foreach($linkedProducts as $product):?>
<?php foreach($linkedBranches[$product->id] as $branchID => $branch):?>
<?php $plans = isset($productPlans[$product->id][$branchID]) ? $productPlans[$product->id][$branchID] : array();?>
<div class="col-sm-4" id="plan<?php echo $i;?>" style="padding-right: 6px;"><?php echo html::select("plans[{$product->id}][{$branchID}][]", $plans, $branches[$product->id][$branchID]->plan, "class='form-control chosen' multiple");?></div>
<?php $i++;?>
<?php endforeach;?>
<?php endforeach;?>
<div class="col-sm-4" id="planDefault" style="padding-right: 6px;"><?php echo html::select("plans[0][0][]", array(), 0, "class='form-control chosen' multiple");?></div>
</div>
</td>
</tr>
<?php $i ++;?>
<?php endforeach;?>
<?php endif;?>
<?php if($project->model == 'waterfall'):?>
<?php $class = (!$project->division and count($linkedProducts) < 2) ? 'hide' : '';?>
+75 -5
View File
@@ -128,6 +128,7 @@ class story extends control
$response['result'] = 'success';
setcookie('lastStoryModule', (int)$this->post->module, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, false);
$storyResult = $this->story->create($objectID, $bugID, $from = isset($fromObjectIDKey) ? $fromObjectIDKey : '', $extra);
if(!$storyResult or dao::isError())
{
@@ -164,7 +165,9 @@ class story extends control
$action = $fromObjectAction;
$extra = $fromObjectID;
}
$actionID = $this->action->create('story', $storyID, $action, '', $extra);
/* Create actions. */
$storyIds = $storyResult['ids'];
foreach($storyIds as $idItem) $actionID = $this->action->create('story', $idItem, $action, '', $extra);
/* Record submit review action. */
$story = $this->dao->findById((int)$storyID)->from(TABLE_STORY)->fetch();
@@ -437,8 +440,7 @@ class story extends control
$this->view->users = $users;
$this->view->moduleID = $moduleID;
$this->view->moduleOptionMenu = $moduleOptionMenu;
$this->view->plans = str_replace('2030-01-01', $this->lang->story->undetermined, $this->loadModel('productplan')->getPairsForStory($productID, $branch, 'skipParent|unexpired|noclosed'));
$this->view->planID = $planID;
$this->view->plans = str_replace('2030-01-01', $this->lang->story->undetermined, $this->loadModel('productplan')->getPairsForStory($productID, $branch == 0 ? '' : $branch, 'skipParent|unexpired|noclosed')); $this->view->planID = $planID;
$this->view->source = $source;
$this->view->sourceNote = $sourceNote;
$this->view->color = $color;
@@ -931,6 +933,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->siblings = empty($story->siblings) ? array() : $this->story->getByList($story->siblings);
$this->view->stories = $stories;
$this->view->users = $users;
$this->view->product = $product;
@@ -1023,6 +1026,16 @@ class story extends control
/* Get edited stories. */
$stories = $this->story->getByList($storyIdList);
/* Filter siblings. */
$siblings = '';
foreach($stories as $id => $story)
{
if(empty($story->siblings)) continue;
$siblings .= "#$id ";
unset($stories[$id]);
}
if(!empty($siblings)) echo js::alert(sprintf($this->lang->story->batchEditTip, $siblings));
$this->loadModel('branch');
if($productID and !$executionID)
{
@@ -1251,6 +1264,8 @@ class story extends control
/* Assign. */
$this->view->title = $this->lang->story->change . "STORY" . $this->lang->colon . $this->view->story->title;
$this->view->siblings = empty($story->siblings) ? array() : $this->story->getByList($story->siblings);
$this->view->branches = $this->loadModel('branch')->getPairs($story->product);
$this->view->users = $this->user->getPairs('pofirst|nodeleted|noclosed', $this->view->story->assignedTo);
$this->view->position[] = $this->lang->story->change;
$this->view->needReview = (($this->app->user->account == $this->view->product->PO or $this->config->story->needReview == 0 or !$this->story->checkForceReview()) and empty($reviewer)) ? "checked='checked'" : "";
@@ -1420,6 +1435,7 @@ class story extends control
$this->view->position = $position;
$this->view->product = $product;
$this->view->branches = $product->type == 'normal' ? array() : $this->loadModel('branch')->getPairs($product->id);
$this->view->siblings = !empty($story->siblings) ? $this->story->getByList($story->siblings) : array();
$this->view->plan = $plan;
$this->view->bugs = $bugs;
$this->view->fromBug = $fromBug;
@@ -1640,7 +1656,7 @@ class story extends control
if(!$this->post->storyIdList) return print(js::locate($this->session->storyList, 'parent'));
$storyIdList = $this->post->storyIdList;
$storyIdList = array_unique($storyIdList);
$actions = $this->story->batchReview($storyIdList, $result, $reason);
$this->story->batchReview($storyIdList, $result, $reason);
if(dao::isError()) return print(js::error(dao::getError()));
if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther');
@@ -1877,8 +1893,15 @@ class story extends control
$stories = $this->story->getByList($storyIdList);
$productStoryList = array();
$productList = array();
$ignoreSiblings = array();
foreach($stories as $story)
{
if(!empty($ignoreSiblings) and isset($ignoreSiblings[$story->id]))
{
unset($stories[$story->id]);
continue;
}
if($story->parent == -1)
{
$skipStory[] = $story->id;
@@ -1893,6 +1916,11 @@ class story extends control
$storyProduct = isset($productList[$story->product]) ? $productList[$story->product] : $this->product->getByID($story->product);
$branch = $storyProduct->type == 'branch' ? ($story->branch > 0 ? $story->branch : '0') : 'all';
if(!isset($productStoryList[$story->product][$story->branch])) $productStoryList[$story->product][$story->branch] = $this->story->getProductStoryPairs($story->product, $branch, 0, 'all', 'id_desc', 0, '', $story->type);
if(!empty($story->siblings))
{
foreach(explode(',', trim($story->siblings, ',')) as $siblingID) $ignoreSiblings[$siblingID] = $siblingID;
}
}
if($this->post->comments)
@@ -1910,6 +1938,8 @@ class story extends control
$actionID = $this->action->create('story', $storyID, 'Closed', htmlSpecialString($this->post->comments[$storyID]), ucfirst($this->post->closedReasons[$storyID]) . ($this->post->duplicateStoryIDList[$storyID] ? ':' . (int)$this->post->duplicateStoryIDList[$storyID] : '') . "|$preStatus");
$this->action->logHistory($actionID, $changes);
if(!empty($stories[$storyID]->siblings)) $this->story->syncSiblings($storyID, $stories[$storyID]->siblings, $changes, 'Closed');
}
}
@@ -2256,12 +2286,27 @@ class story extends control
{
if(!empty($_POST) && isset($_POST['storyIdList']))
{
$allChanges = $this->story->batchAssignTo();
$allChanges = $this->story->batchAssignTo();
if(dao::isError()) return print(js::error(dao::getError()));
$assignedSiblings = array();
$oldStories = $this->story->getByList($this->post->storyIdList);
foreach($allChanges as $storyID => $changes)
{
$actionID = $this->action->create('story', $storyID, 'Assigned', '', $this->post->assignedTo);
$this->action->logHistory($actionID, $changes);
/* Sync siblings. */
if(!empty($oldStories[$storyID]->siblings))
{
$siblings = $oldStories[$storyID]->siblings;
foreach(explode(',', $siblings) as $siblingID)
{
if(in_array($siblingID, $this->post->storyIdList) or isset($assignedSiblings[$siblingID])) $siblings = str_replace(",$siblingID,", ',', $siblings);
}
$this->story->syncSiblings($storyID, trim($siblings, ','), $changes, 'Assigned');
foreach(explode(',', trim($siblings, ',')) as $assignedID) $assignedSiblings[$assignedID] = $assignedID;
}
}
}
if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther');
@@ -2970,4 +3015,29 @@ class story extends control
return print(html::select('URS[]', $URS, $requirementList, "class='form-control chosen' multiple"));
}
/**
* AJAX: Deleted story sibling.
*
* @access public
* @return void
*/
public function ajaxRelieveSibling()
{
$siblingID = !empty($_POST['siblingID']) ? $_POST['siblingID'] : 0;
$story = $this->story->getByID($siblingID);
$siblings = explode(',', trim($story->siblings, ','));
if(empty($story->siblings)) return $this->send(array('result' => 'fail'));
/* batchUnset siblingID from siblings.*/
$replaceSql = "UPDATE " . TABLE_STORY . " SET siblings = REPLACE(siblings,',$siblingID,', ',') WHERE `product` = $story->product";
$this->dbh->exec($replaceSql);
/* Update siblings to empty by siblingID and if siblings eq ','.*/
$this->dao->update(TABLE_STORY)->set('siblings')->eq('')->where('id')->eq($siblingID)->orWhere('siblings')->eq(',')->exec();
if(!dao::isError()) $this->loadModel('action')->create('story', $siblingID, 'relieved');
return $this->send(array('result' => 'success', 'silbingsCount' => count($siblings)-1));
}
}
+3 -1
View File
@@ -2,4 +2,6 @@
#affectedProjects .table + h6 {border-top: 1px solid #ddd;}
.nav-tabs>li>a {height: 36px;}
.table-form>tbody>tr>th {width: 100px !important;}
.colorpicker {right: 7px; top: 7px; opacity: 1;}
.colorpicker {right: 7px; top: 7px; opacity: 1;}
#siblingList td, #siblingTitle td {padding-left: 65px;}
#siblingTitle td {font-weight: 700;}
+13 -1
View File
@@ -1,3 +1,4 @@
.icon-exclamation-sign {color: #0075ff;}
.linkbox {height: 180px; overflow-y: auto;}
.tab-pane h6 {margin: 0px; padding: 10px;}
.tab-pane h6, .tab-pane table {border: 1px solid #ddd; border-top: none;}
@@ -5,7 +6,6 @@
.input-group .control-product + .chosen-container-single .chosen-single {border-radius: 2px 0 0 2px; border-right-width: 1px; padding-right: 1px;}
.input-group .control-product + .chosen-container-active .chosen-single {border-right-width: 1px; padding-right: 0;}
.input-group .control-branch + .chosen-container-single .chosen-single {border-radius: 0 2px 2px 0; border-left-width: 0px;}
#batchCreateForm .input-group, #batchEditForm .input-group,
#batchCreateForm .input-group .form-control, #batchEditForm .input-group .form-control {position: static;}
@@ -17,3 +17,15 @@
.c-source {width: 130px}
.c-note {width: 100px}
.c-category {width: 90px}
.icon-info {color: #ff9800; border-color: #ff9800;}
.text-active {text-decoration: underline;}
.text-cancel {text-decoration: underline;color:#838a9d;margin-left:10px;}
.popover {border:unset;}
.popover.bottom .arrow{border-bottom-color:#ffffff;}
.popover-icon {float:left;}
.popover .content {margin-left:25px;overflow: overlay;}
.hide {display:none !important;}
.sibling > a.viewlink {padding:6px;white-space: nowrap;overflow: hidden;display: inline-flex;max-width: 70%;}
.sibling > span {max-width: 60px;white-space: nowrap;overflow: hidden;}
.tooltip-inner {max-width: 500px;}
+4
View File
@@ -16,3 +16,7 @@
.source .input-group-addon, .sourceNote .input-group-addon {padding-top: 9px;}
#sourceNoteBox {width: 80px;}
#moduleIdBox .input-group-addon {min-width: 77px;}
.btn.btn-action, .c-actions .btn {color: grey}
#storyNoticeBranch {font-size: 13px; color: #5e626d;}
#storyNoticeBranch i {font-size: 14px; color: #0075ff; margin-right: 3px;}
+1
View File
@@ -5,3 +5,4 @@
.needNotReviewBox {width: 130px;}
.needNotReviewBox span.input-group-addon {border: 1px solid #dcdcdc; border-left-width: 0px; width:130px;}
#moduleIdBox .chosen-container, #planIdBox .chosen-container {min-width: unset;}
#legendSiblings .form-group {margin-top: 10px;}
-1
View File
@@ -11,5 +11,4 @@
.linkStoryTitle {white-space: nowrap; text-overflow: ellipsis; overflow: hidden;}
.linkStoryTr > th {width: 90px;}
.linkStoryTr > td {padding-left: 0px !important;}
.removeButton {margin-left: -10px;}
.resolution {white-space: nowrap; overflow: hidden;}
+1
View File
@@ -1,5 +1,6 @@
$(function()
{
$('[data-toggle="tooltip"]').tooltip();
$('#needNotReview').on('change', function()
{
$('#reviewer').val($(this).is(':checked') ? '' : lastReviewer).attr('disabled', $(this).is(':checked') ? 'disabled' : null).trigger('chosen:updated');
+46
View File
@@ -94,3 +94,49 @@ function loadURS()
$('#URS').chosen();
});
}
$('.sibling').mouseover(function() {
$(this).parent('ul').find('a.unlink').addClass('hide');
$(this).find('.unlink').removeClass('hide');
});
$('.sibling').mouseenter(function() {
$('[data-toggle="popover"]').popover('hide');
});
$('.sibling').mouseout(function() {
$(this).find('.unlink').addClass('hide');
});
$('[data-toggle="popover"]').each(function(item) {
$index = $(this).attr('data-id');
$(this).popover({
placement: 'bottom',
html: true,
content: '<div class="popover-icon"><i class="icon-info"></i></div><div class="content">' + relievedTip + '</div><div class="popover-custom text-right"><a href="javascript:relieve(' + $index + ')" class="text-active btn-info">' + relieved + '</a> <a href="javascript:popoverCancel(' + $index + ');" class="text-cancel">' + cancel + '</a></div>'
});
})
function relieve(index)
{
$.post(relieveURL, {siblingID:index}, function(data){
$('[data-id="' + index + '"]').popover('hide');
if(data.result == 'success')
{
if(data.silbingsCount != 0) $('[data-id="' + index + '"]').parent('li').remove();
if(data.silbingsCount == 0 || index == storyID)
{
$('[href="#legendSiblings"]').parent('li').next('li').addClass('active');;
$('[href="#legendSiblings"]').parent('li').remove();
$('#legendSiblings').next('div').addClass('active');
$('#legendSiblings').remove();
$('#siblingTitle').remove();
$('#siblingList').remove();
}
}
}, 'json');
}
function popoverCancel(index)
{
$('[data-id="' + index + '"]').popover('hide').addClass('hide');
}
+224
View File
@@ -60,6 +60,18 @@ $(function()
});
$(document).on('change', '#module', loadURS);
if($(".table-form select[id^='branches']").length == $('.switchBranch #branchBox option').length)
{
$('.table-col .icon-plus').parent().css('pointer-events', 'none')
$('.table-col .icon-plus').parent().addClass('disabled')
}
$.get(createLink('product', 'ajaxGetProductById', "productID=" + $('#product').val()), function(data)
{
$.cookie('branchSourceName', data.branchSourceName)
$.cookie('branchName', data.branchName)
}, 'json')
});
/**
@@ -108,3 +120,215 @@ function setLane(regionID)
$(window).unload(function(){
if(blockID) window.parent.refreshBlock($('#block' + blockID));
});
/**
* Add branch box.
*
* @param obj $obj
* @access public
* @return void
*/
function addBranchesBox(obj)
{
if($(".table-form select[id^='branches']").length == $('.switchBranch #branchBox option').length) return false;
var selectedVal = [];
$(".table-form select[id^='branches']").each(function()
{
var selectedProduct = $(this).val();
if($.inArray(selectedProduct, selectedVal) < 0) selectedVal.push(selectedProduct);
});
var branch = 0;
$('.table-form #branches0 option').each(function(){
if($.inArray($(this).val(), selectedVal) < 0)
{
branch = $(this).val();
return false;
}
});
var item = $('#addBranchesBox').html().replace(/%i%/g, itemIndex);
$(obj).closest('tr').after('<tr class="addBranchesBox' + itemIndex + '">' + item + '</tr>');
$('#branches_i__chosen').remove();
$('#branches' + itemIndex).chosen();
$('#modules_i__chosen').remove();
$('#modules' + itemIndex).chosen();
$('#plans_i__chosen').remove();
$('#plans' + itemIndex).chosen();
$('.addBranchesBox' + itemIndex + ' #planIdBox').css('flex', '0 0 ' + gap + 'px');
$.ajaxSettings.async = false;
loadBranchForSiblings($('#product').val(), branch, itemIndex)
loadModuleForSiblings($('#product').val(), branch, itemIndex)
loadPlanForSiblings($('#product').val(), branch, itemIndex)
$.ajaxSettings.async = true;
$('.addBranchesBox' + itemIndex + ' #branchBox .input-group .input-group-addon').html($.cookie('branchSourceName'))
disableSelectedBranches();
if($(".table-form select[id^='branches']").length == $('.switchBranch #branchBox option').length)
{
$('.table-col .icon-plus').parent().css('pointer-events', 'none')
$('.table-col .icon-plus').parent().addClass('disabled')
}
itemIndex ++;
}
/**
* Delete branch box.
*
* @param obj $obj
* @access public
* @return void
*/
function deleteBranchesBox(obj)
{
$(obj).closest('tr').remove();
disableSelectedBranches();
$('.icon-plus').parent().css('pointer-events', 'auto')
$('.icon-plus').parent().removeClass('disabled')
}
/**
* Make the selected branch non clickable.
*
* @return void
*/
function disableSelectedBranches()
{
$(".table-form select[id^='branches'] option[disabled='disabled']").removeAttr('disabled');
var selectedVal = [];
$(".table-form select[id^='branches']").each(function()
{
var selectedBranch = $(this).val();
if($.inArray(selectedBranch, selectedVal) < 0) selectedVal.push(selectedBranch);
})
$(".table-form select[id^='branches']").each(function()
{
var selectedBranch = $(this).val();
$(this).find('option').each(function()
{
var optionVal = $(this).attr('value');
if(optionVal != selectedBranch && $.inArray(optionVal, selectedVal) >= 0) $(this).attr('disabled', 'disabled');
})
})
$(".table-form select[id^=branches]").trigger('chosen:updated');
}
/**
* Load branch for multi branch or multi platform.
*
* @param int $branch
* @param int $branchIndex
* @access public
* @return void
*/
function loadBranchRelation(branch, branchIndex)
{
var productID = $('#product').val();
if(typeof(branch) == 'undefined') branch = 0;
$.ajaxSettings.async = false;
loadModuleForSiblings(productID, branch, branchIndex)
loadPlanForSiblings(productID, branch, branchIndex)
$.ajaxSettings.async = true;
disableSelectedBranches()
}
/**
* Load branch for siblings.
*
* @paran int $procutID
* @param int $branch
* @param int $branchIndex
* @access public
* @return void
*/
function loadBranchForSiblings(productID, branch, branchIndex)
{
var isSiblings = storyType == 'story' ? 'yes' : 'no';
$.get(createLink('branch', 'ajaxGetBranches', "productID=" + productID + "&oldBranch=" + branch + "&param=active&projectID=" + executionID + "&withMainBranch=1&isSiblings=" + isSiblings + "&fieldID=" + branchIndex), function(data)
{
if(data)
{
/* reload branch */
$('#branches' + branchIndex).replaceWith(data);
$('#branches' + branchIndex + "_chosen").remove();
$('#branches' + branchIndex).next('.picker').remove();
$('#branches' + branchIndex).chosen();
}
})
}
/**
* Load module for siblings.
*
* @paran int $procutID
* @param int $branch
* @param int $branchIndex
* @access public
* @return void
*/
function loadModuleForSiblings(productID, branch, branchIndex)
{
/* Load module */
var currentModule = 0;
var moduleLink = createLink('tree', 'ajaxGetOptionMenu', 'productID=' + productID + '&viewtype=story&branch=' + branch + '&rootModuleID=0&returnType=html&fieldID=' + branchIndex + '&needManage=false&extra=&currentModuleID=' + currentModule);
if(branchIndex > 0)
{
var $moduleIDBox = $('.addBranchesBox'+ branchIndex +' #moduleIdBox');
}
else
{
var $moduleIDBox = $('.switchBranch #moduleIdBox');
}
$moduleIDBox.load(moduleLink, function()
{
$moduleIDBox.find('#modules' + branchIndex).chosen();
if(branchIndex == 0)
{
$('.switchBranch #moduleIdBox > span:first-child').remove()
}
$moduleIDBox.prepend("<span class='input-group-addon fix-border'>" + storyModule + "</span>" );
$moduleIDBox.fixInputGroup();
});
}
/**
* Load plan for siblings.
*
* @paran int $procutID
* @param int $branch
* @param int $branchIndex
* @access public
* @return void
*/
function loadPlanForSiblings(productID, branch, branchIndex)
{
/* Load plan */
if(branch == '0') branch = '';
planLink = createLink('product', 'ajaxGetPlans', 'productID=' + productID + '&branch=' + branch + '&planID=0&fieldID=' + branchIndex + '&needCreate=false&expired=unexpired&param=skipParent|forStory,' + config.currentMethod);
if(branchIndex > 0)
{
var $planIdBox = $('.addBranchesBox'+ branchIndex +' #planIdBox');
}
else
{
var $planIdBox = $('#planIdBox');
}
$planIdBox.load(planLink, function()
{
$planIdBox.find('#plans' + branchIndex).chosen();
$planIdBox.prepend("<span class='input-group-addon fix-border'>" + storyPlan + "</span>");
$planIdBox.fixInputGroup();
});
}
+1
View File
@@ -1,5 +1,6 @@
$(function()
{
$('[data-toggle="tooltip"]').tooltip();
$('.main-side #branch').closest('td').find('#product_chosen .chosen-single').css('width', '153px');
if(storyStatus == 'reviewing')
+9 -12
View File
@@ -9,17 +9,14 @@ $(function()
/* Add for task #5385. */
if(config.onlybody == 'yes') $('.main-actions').css('width', '100%')
$('#unlinkStory').click(function()
$('.legendStories').mouseover(function()
{
if($('.removeButton').hasClass('hide'))
{
$('.removeButton').removeClass('hide');
$(this).text(cancel);
}
else
{
$('.removeButton').addClass('hide');
$(this).text(unlink);
}
})
$(this).parent('ul').find('a.removeButton').addClass('hide');
$(this).find('.removeButton').removeClass('hide');
});
$('.legendStories').mouseout(function()
{
$(this).find('.removeButton').addClass('hide');
});
});
+32 -1
View File
@@ -12,7 +12,7 @@
global $config;
$lang->story->create = "Story hinzufügen";
$lang->story->requirement = zget($lang, 'URCommon', "Requirement");
$lang->story->requirement = 'UR';
$lang->story->story = zget($lang, 'SRCommon', "Story");
$lang->story->createStory = 'Create ' . $lang->story->story;
$lang->story->createRequirement = 'Create ' . $lang->story->requirement;
@@ -76,6 +76,10 @@ $lang->story->undetermined = 'undetermined';
$lang->story->order = 'Order';
$lang->story->saveDraft = 'Save as draft';
$lang->story->doNotSubmit = 'Do Not Submit';
$lang->story->currentBranch = 'Current Branch';
$lang->story->siblings = 'Siblings story';
$lang->story->relieved = 'Relieved';
$lang->story->relievedSiblings = 'Relieved Siblings';
$lang->story->editAction = "Edit {$lang->SRCommon}";
$lang->story->changeAction = "Change Story";
@@ -96,6 +100,15 @@ $lang->story->storyRound = '%s time estimation';
$lang->story->float = "『%s』should be positive number, decimals included.";
$lang->story->saveDraftSuccess = 'Save as draft succeeded.';
$lang->story->changeSyncTip = "The modification of this story will be synchronized to the following twin requirements";
$lang->story->syncTip = "The twin story are synchronized except for product, branch, module, plan, and stage. After the twin relationship is dissolved, they are no longer synchronized.";
$lang->story->relievedTip = "The twin relationship cannot be restored after dissolving, the content of the demand is no longer synchronized, whether to dissolving?";
$lang->story->assignSyncTip = "Both twin stories modify the assignor synchronously";
$lang->story->closeSyncTip = "Twin stories are closed synchronously";
$lang->story->activateSyncTip = "Twin stories are activated synchronously";
$lang->story->relievedSiblingsTip = "After product adjustment, the twin relationship of this story will be automatically removed, and the story will no longer be synchronized. Do you want to save?";
$lang->story->batchEditTip = "{$lang->SRCommon} %sis twin stories, and this operation has been filtered.";
$lang->story->id = 'ID';
$lang->story->parent = 'Parent';
$lang->story->product = $lang->productCommon;
@@ -308,6 +321,7 @@ $lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist';
$lang->story->confirmRecallChange = "After undo the change, the story content will revert to the version before the change. Are you sure you want to undo?";
$lang->story->confirmRecallReview = "Are you sure you want to withdraw the review?";
$lang->story->noStoryToTask = "Only the activated {$lang->SRCommon} can be converted into a task!";
$lang->story->ignoreClosedStory = "{$lang->SRCommon} %s status is closed, and the operation has been filtered.";
$lang->story->form = new stdclass();
$lang->story->form->area = 'Story Bereich';
@@ -341,6 +355,7 @@ $lang->story->action->subdividestory = array('main' => '$date, aufgeteilt
$lang->story->action->unlinkrelatedstory = array('main' => '$date, Verknüpfung aufgelöst durch <strong>$actor</strong> von Story <strong>$extra</strong>.');
$lang->story->action->unlinkchildstory = array('main' => '$date, Verknüpfung aufgelöst durch <strong>$actor</strong> Story <strong>$extra</strong>.');
$lang->story->action->recalledchange = array('main' => '$date, Undo changes by <strong>\$actor</strong>.');
$lang->story->action->syncsiblings = array('main' => "\$date, the system judges that this story is adjusted synchronously due to the \$operate of twin story <strong>\$extra</strong>.", 'operate' => 'operateList');
/* Statistical statement. */
$lang->story->report = new stdclass();
@@ -429,6 +444,7 @@ $lang->story->chosen->reviewedBy = 'Prüfer wählen';
$lang->story->notice = new stdClass();
$lang->story->notice->closed = 'Die ausgewählten Storys wurden bereits geschlossen!';
$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.';
$lang->story->notice->changePlan = 'The plan can be changed to only one item.';
$lang->story->convertToTask = new stdClass();
$lang->story->convertToTask->fieldList = array();
@@ -466,6 +482,17 @@ $lang->story->featureBar['browse']['unclosed'] = $lang->story->unclosed;
$lang->story->featureBar['browse']['draft'] = $lang->story->statusList['draft'];
$lang->story->featureBar['browse']['reviewing'] = $lang->story->statusList['reviewing'];
$lang->story->operateList = array();
$lang->story->operateList['assigned'] = 'assigned';
$lang->story->operateList['closed'] = 'closed';
$lang->story->operateList['activated'] = 'activated';
$lang->story->operateList['changed'] = 'changed';
$lang->story->operateList['reviewed'] = 'reviewed';
$lang->story->operateList['edited'] = 'edited';
$lang->story->operateList['submitreview'] = 'submit review';
$lang->story->operateList['recalledchange'] = 'recalled change';
$lang->story->operateList['recalled'] = 'recalled review';
$lang->requirement->common = $lang->URCommon;
$lang->requirement->create = 'Create Requirement';
$lang->requirement->batchCreate = "Batch Create";
@@ -490,3 +517,7 @@ $lang->requirement->batchAssignTo = "Batch Assign";
$lang->requirement->batchChangeModule = "Batch Change Modules";
$lang->requirement->submitReview = $lang->story->submitReview;
$lang->requirement->linkStory = 'Link Story';
$lang->story->addBranch = 'Add %s';
$lang->story->deleteBranch = 'Delete %s';
$lang->story->notice->branch = 'Each branch will establish a requirement, and the requirements are twins. The twins requirements are synchronized except for the product, branch, module, plan, and stage fields. You can manually remove the twins relationship later';
+32 -1
View File
@@ -12,7 +12,7 @@
global $config;
$lang->story->create = "Create Story";
$lang->story->requirement = zget($lang, 'URCommon', "Requirement");
$lang->story->requirement = 'UR';
$lang->story->story = zget($lang, 'SRCommon', "Story");
$lang->story->createStory = 'Create ' . $lang->story->story;
$lang->story->createRequirement = 'Create ' . $lang->story->requirement;
@@ -76,6 +76,10 @@ $lang->story->undetermined = 'undetermined';
$lang->story->order = 'Order';
$lang->story->saveDraft = 'Save as draft';
$lang->story->doNotSubmit = 'Do Not Submit';
$lang->story->currentBranch = 'Current Branch';
$lang->story->siblings = 'Siblings story';
$lang->story->relieved = 'Relieved';
$lang->story->relievedSiblings = 'Relieved Siblings';
$lang->story->editAction = "Edit {$lang->SRCommon}";
$lang->story->changeAction = "Change {$lang->SRCommon}";
@@ -96,6 +100,15 @@ $lang->story->storyRound = '%s time estimation';
$lang->story->float = "『%s』should have positive number, or decimals.";
$lang->story->saveDraftSuccess = 'Save as draft succeeded.';
$lang->story->changeSyncTip = "The modification of this story will be synchronized to the following twin requirements";
$lang->story->syncTip = "The twin story are synchronized except for product, branch, module, plan, and stage. After the twin relationship is dissolved, they are no longer synchronized.";
$lang->story->relievedTip = "The twin relationship cannot be restored after dissolving, the content of the demand is no longer synchronized, whether to dissolving?";
$lang->story->assignSyncTip = "Both twin stories modify the assignor synchronously";
$lang->story->closeSyncTip = "Twin stories are closed synchronously";
$lang->story->activateSyncTip = "Twin stories are activated synchronously";
$lang->story->relievedSiblingsTip = "After product adjustment, the twin relationship of this story will be automatically removed, and the story will no longer be synchronized. Do you want to save?";
$lang->story->batchEditTip = "{$lang->SRCommon} %sis twin stories, and this operation has been filtered.";
$lang->story->id = 'ID';
$lang->story->parent = 'Parent';
$lang->story->product = $lang->productCommon;
@@ -308,6 +321,7 @@ $lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist';
$lang->story->confirmRecallChange = "After undo the change, the story content will revert to the version before the change. Are you sure you want to undo?";
$lang->story->confirmRecallReview = "Are you sure you want to withdraw the review?";
$lang->story->noStoryToTask = "Only the activated {$lang->SRCommon} can be converted into a task!";
$lang->story->ignoreClosedStory = "{$lang->SRCommon} %s status is closed, and the operation has been filtered.";
$lang->story->form = new stdclass();
$lang->story->form->area = 'Scope';
@@ -341,6 +355,7 @@ $lang->story->action->subdividestory = array('main' => '$date, decomposed
$lang->story->action->unlinkrelatedstory = array('main' => '$date, unlinked by <strong>$actor</strong> from Story <strong>$extra</strong>.');
$lang->story->action->unlinkchildstory = array('main' => '$date, unlinked by <strong>$actor</strong> Decomposed Story <strong>$extra</strong>.');
$lang->story->action->recalledchange = array('main' => '$date, Undo changes by <strong>\$actor</strong>.');
$lang->story->action->syncsiblings = array('main' => "\$date, the system judges that this story is adjusted synchronously due to the \$operate of twin story <strong>\$extra</strong>.", 'operate' => 'operateList');
/* Statistical statement. */
$lang->story->report = new stdclass();
@@ -429,6 +444,7 @@ $lang->story->chosen->reviewedBy = 'Choose ReviewedBy';
$lang->story->notice = new stdClass();
$lang->story->notice->closed = 'Story that you select is closed!';
$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.';
$lang->story->notice->changePlan = 'The plan can be changed to only one item.';
$lang->story->convertToTask = new stdClass();
$lang->story->convertToTask->fieldList = array();
@@ -466,6 +482,17 @@ $lang->story->featureBar['browse']['unclosed'] = $lang->story->unclosed;
$lang->story->featureBar['browse']['draft'] = $lang->story->statusList['draft'];
$lang->story->featureBar['browse']['reviewing'] = $lang->story->statusList['reviewing'];
$lang->story->operateList = array();
$lang->story->operateList['assigned'] = 'assigned';
$lang->story->operateList['closed'] = 'closed';
$lang->story->operateList['activated'] = 'activated';
$lang->story->operateList['changed'] = 'changed';
$lang->story->operateList['reviewed'] = 'reviewed';
$lang->story->operateList['edited'] = 'edited';
$lang->story->operateList['submitreview'] = 'submit review';
$lang->story->operateList['recalledchange'] = 'recalled change';
$lang->story->operateList['recalled'] = 'recalled review';
$lang->requirement->common = $lang->URCommon;
$lang->requirement->create = 'Create Requirement';
$lang->requirement->batchCreate = "Batch Create";
@@ -490,3 +517,7 @@ $lang->requirement->batchAssignTo = "Batch Assign";
$lang->requirement->batchChangeModule = "Batch Change Modules";
$lang->requirement->submitReview = $lang->story->submitReview;
$lang->requirement->linkStory = 'Link Story';
$lang->story->addBranch = 'Add %s';
$lang->story->deleteBranch = 'Delete %s';
$lang->story->notice->branch = 'Each branch will establish a requirement, and the requirements are twins. The twins requirements are synchronized except for the product, branch, module, plan, and stage fields. You can manually remove the twins relationship later';
+32 -1
View File
@@ -12,7 +12,7 @@
global $config;
$lang->story->create = "Créer Story";
$lang->story->requirement = zget($lang, 'URCommon', "Requirement");
$lang->story->requirement = 'UR';
$lang->story->story = zget($lang, 'SRCommon', "Story");
$lang->story->createStory = 'Create ' . $lang->story->story;
$lang->story->createRequirement = 'Create ' . $lang->story->requirement;
@@ -76,6 +76,10 @@ $lang->story->undetermined = 'undetermined';
$lang->story->order = 'Order';
$lang->story->saveDraft = 'Save as draft';
$lang->story->doNotSubmit = 'Do Not Submit';
$lang->story->currentBranch = 'Current Branch';
$lang->story->siblings = 'Siblings story';
$lang->story->relieved = 'Relieved';
$lang->story->relievedSiblings = 'Relieved Siblings';
$lang->story->editAction = "Edit {$lang->SRCommon}";
$lang->story->changeAction = "Changer Story";
@@ -96,6 +100,15 @@ $lang->story->storyRound = '%s time estimation';
$lang->story->float = "『 %s 』doit avoir des nombres positifs ou décimaux.";
$lang->story->saveDraftSuccess = 'Save as draft succeeded.';
$lang->story->changeSyncTip = "The modification of this story will be synchronized to the following twin requirements";
$lang->story->syncTip = "The twin story are synchronized except for product, branch, module, plan, and stage. After the twin relationship is dissolved, they are no longer synchronized.";
$lang->story->relievedTip = "The twin relationship cannot be restored after dissolving, the content of the demand is no longer synchronized, whether to dissolving?";
$lang->story->assignSyncTip = "Both twin stories modify the assignor synchronously";
$lang->story->closeSyncTip = "Twin stories are closed synchronously";
$lang->story->activateSyncTip = "Twin stories are activated synchronously";
$lang->story->relievedSiblingsTip = "After product adjustment, the twin relationship of this story will be automatically removed, and the story will no longer be synchronized. Do you want to save?";
$lang->story->batchEditTip = "{$lang->SRCommon} %sis twin stories, and this operation has been filtered.";
$lang->story->id = 'ID';
$lang->story->parent = 'Parent';
$lang->story->product = $lang->productCommon;
@@ -308,6 +321,7 @@ $lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist';
$lang->story->confirmRecallChange = "After undo the change, the story content will revert to the version before the change. Are you sure you want to undo?";
$lang->story->confirmRecallReview = "Are you sure you want to withdraw the review?";
$lang->story->noStoryToTask = "Only the activated {$lang->SRCommon} can be converted into a task!";
$lang->story->ignoreClosedStory = "{$lang->SRCommon} %s status is closed, and the operation has been filtered.";
$lang->story->form = new stdclass();
$lang->story->form->area = 'Périmètre';
@@ -341,6 +355,7 @@ $lang->story->action->subdividestory = array('main' => '$date, décompos
$lang->story->action->unlinkrelatedstory = array('main' => '$date, dissociée par <strong>$actor</strong> de la Story <strong>$extra</strong>.');
$lang->story->action->unlinkchildstory = array('main' => '$date, dissociée par <strong>$actor</strong> de la sous-Story <strong>$extra</strong>.');
$lang->story->action->recalledchange = array('main' => '$date, Undo changes by <strong>\$actor</strong>.');
$lang->story->action->syncsiblings = array('main' => "\$date, the system judges that this story is adjusted synchronously due to the \$operate of twin story <strong>\$extra</strong>.", 'operate' => 'operateList');
/* Statistical statement. */
$lang->story->report = new stdclass();
@@ -429,6 +444,7 @@ $lang->story->chosen->reviewedBy = 'Choisir valideur';
$lang->story->notice = new stdClass();
$lang->story->notice->closed = 'La Story que vous avez sélectionnée est malheureusement fermée !';
$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.';
$lang->story->notice->changePlan = 'The plan can be changed to only one item.';
$lang->story->convertToTask = new stdClass();
$lang->story->convertToTask->fieldList = array();
@@ -466,6 +482,17 @@ $lang->story->featureBar['browse']['unclosed'] = $lang->story->unclosed;
$lang->story->featureBar['browse']['draft'] = $lang->story->statusList['draft'];
$lang->story->featureBar['browse']['reviewing'] = $lang->story->statusList['reviewing'];
$lang->story->operateList = array();
$lang->story->operateList['assigned'] = 'assigned';
$lang->story->operateList['closed'] = 'closed';
$lang->story->operateList['activated'] = 'activated';
$lang->story->operateList['changed'] = 'changed';
$lang->story->operateList['reviewed'] = 'reviewed';
$lang->story->operateList['edited'] = 'edited';
$lang->story->operateList['submitreview'] = 'submit review';
$lang->story->operateList['recalledchange'] = 'recalled change';
$lang->story->operateList['recalled'] = 'recalled review';
$lang->requirement->common = $lang->URCommon;
$lang->requirement->create = 'Create Requirement';
$lang->requirement->batchCreate = "Batch Create";
@@ -490,3 +517,7 @@ $lang->requirement->batchAssignTo = "Batch Assign";
$lang->requirement->batchChangeModule = "Batch Change Modules";
$lang->requirement->submitReview = $lang->story->submitReview;
$lang->requirement->linkStory = 'Lier Story';
$lang->story->addBranch = 'Add %s';
$lang->story->deleteBranch = 'Delete %s';
$lang->story->notice->branch = 'Each branch will establish a requirement, and the requirements are twins. The twins requirements are synchronized except for the product, branch, module, plan, and stage fields. You can manually remove the twins relationship later';
+20
View File
@@ -94,6 +94,14 @@ $lang->story->storyRound = '%s time estimation';
$lang->story->float = "『%s』should be positive number, decimals included.";
$lang->story->saveDraftSuccess = 'Save as draft succeeded.';
$lang->story->changeSyncTip = "The modification of this story will be synchronized to the following twin requirements";
$lang->story->syncTip = "The twin story are synchronized except for product, branch, module, plan, and stage. After the twin relationship is dissolved, they are no longer synchronized.";
$lang->story->assignSyncTip = "Both twin stories modify the assignor synchronously";
$lang->story->closeSyncTip = "Twin stories are closed synchronously";
$lang->story->activateSyncTip = "Twin stories are activated synchronously";
$lang->story->relievedSiblingsTip = "After product adjustment, the twin relationship of this story will be automatically removed, and the story will no longer be synchronized. Do you want to save?";
$lang->story->batchEditTip = "{$lang->SRCommon} %sis twin stories, and this operation has been filtered.";
$lang->story->id = 'ID';
$lang->story->parent = 'Mẹ';
$lang->story->product = $lang->productCommon;
@@ -332,6 +340,7 @@ $lang->story->action->subdividestory = array('main' => '$date, được p
$lang->story->action->unlinkrelatedstory = array('main' => '$date, bị hủy bởi <strong>$actor</strong> từ Câu chuyện <strong>$extra</strong>.');
$lang->story->action->unlinkchildstory = array('main' => '$date, bị hủy bởi <strong>$actor</strong> Câu chuyện được phân rã <strong>$extra</strong>.');
$lang->story->action->recalledchange = array('main' => '$date, Undo changes by <strong>\$actor</strong>.');
$lang->story->action->syncsiblings = array('main' => "\$date, the system judges that this story is adjusted synchronously due to the \$operate of twin story <strong>\$extra</strong>.", 'operate' => 'operateList');
/* Statistical statement. */
$lang->story->report = new stdclass();
@@ -456,3 +465,14 @@ $lang->story->featureBar['browse']['all'] = $lang->all;
$lang->story->featureBar['browse']['unclosed'] = $lang->story->unclosed;
$lang->story->featureBar['browse']['draft'] = $lang->story->statusList['draft'];
$lang->story->featureBar['browse']['reviewing'] = $lang->story->statusList['reviewing'];
$lang->story->operateList = array();
$lang->story->operateList['assigned'] = 'assigned';
$lang->story->operateList['closed'] = 'closed';
$lang->story->operateList['activated'] = 'activated';
$lang->story->operateList['changed'] = 'changed';
$lang->story->operateList['reviewed'] = 'reviewed';
$lang->story->operateList['edited'] = 'edited';
$lang->story->operateList['submitreview'] = 'submit review';
$lang->story->operateList['recalledchange'] = 'recalled change';
$lang->story->operateList['recalled'] = 'recalled review';
+31
View File
@@ -76,6 +76,10 @@ $lang->story->undetermined = '待定';
$lang->story->order = '排序';
$lang->story->saveDraft = '存为草稿';
$lang->story->doNotSubmit = '保存暂不提交';
$lang->story->currentBranch = '当前%s';
$lang->story->siblings = '孪生需求';
$lang->story->relieved = '解除';
$lang->story->relievedSiblings = '解除孪生需求';
$lang->story->editAction = "编辑{$lang->SRCommon}";
$lang->story->changeAction = "变更{$lang->SRCommon}";
@@ -96,6 +100,15 @@ $lang->story->storyRound = '第 %s 轮估算';
$lang->story->float = "『%s』应当是正数,可以是小数。";
$lang->story->saveDraftSuccess = '存为草稿成功';
$lang->story->changeSyncTip = "该需求的修改会同步到如下的孪生需求";
$lang->story->syncTip = "孪生需求间除产品、分支 、模块、计划、阶段外均同步,孪生关系解除后不再同步";
$lang->story->relievedTip = "孪生关系解除后无法恢复,需求的内容不再同步,是否解除?";
$lang->story->assignSyncTip = "孪生需求均同步修改指派人";
$lang->story->closeSyncTip = "孪生需求均同步关闭";
$lang->story->activateSyncTip = "孪生需求均同步激活";
$lang->story->relievedSiblingsTip = '产品调整后,本需求自动解除孪生关系,需求不再同步,是否保存?';
$lang->story->batchEditTip = "{$lang->SRCommon} %s为孪生需求,本次操作已被过滤。";
$lang->story->id = '编号';
$lang->story->parent = '父需求';
$lang->story->product = "所属{$lang->productCommon}";
@@ -308,6 +321,7 @@ $lang->story->errorDuplicateStory = $lang->SRCommon . '%s不存在';
$lang->story->confirmRecallChange = "撤销变更后,需求内容会回退至变更前的版本,您确定要撤销吗?";
$lang->story->confirmRecallReview = "您确定要撤回评审吗?";
$lang->story->noStoryToTask = "只有激活的{$lang->SRCommon}才能转为任务!";
$lang->story->ignoreClosedStory = "{$lang->SRCommon} %s 状态为已关闭,本次操作已被过滤。";
$lang->story->form = new stdclass();
$lang->story->form->area = "该{$lang->SRCommon}所属范围";
@@ -341,6 +355,7 @@ $lang->story->action->subdividestory = array('main' => "\$date, 由 <stro
$lang->story->action->unlinkrelatedstory = array('main' => "\$date, 由 <strong>\$actor</strong> 移除相关{$lang->SRCommon} <strong>\$extra</strong>。");
$lang->story->action->unlinkchildstory = array('main' => "\$date, 由 <strong>\$actor</strong> 移除细分{$lang->SRCommon} <strong>\$extra</strong>。");
$lang->story->action->recalledchange = array('main' => "\$date, 由 <strong>\$actor</strong> 撤销变更。");
$lang->story->action->syncsiblings = array('main' => "\$date, 系统判断由于孪生需求 <strong>\$extra</strong> \$operate,本需求同步调整。", 'operate' => 'operateList');
/* 统计报表。*/
$lang->story->report = new stdclass();
@@ -429,6 +444,7 @@ $lang->story->chosen->reviewedBy = '选择评审人...';
$lang->story->notice = new stdClass();
$lang->story->notice->closed = "您选择的{$lang->SRCommon}已经被关闭了!";
$lang->story->notice->reviewerNotEmpty = '该需求需要评审,评审人员不能为空。';
$lang->story->notice->changePlan = '所属计划只能改为一条,修改后才能保存成功。';
$lang->story->convertToTask = new stdClass();
$lang->story->convertToTask->fieldList = array();
@@ -466,6 +482,17 @@ $lang->story->featureBar['browse']['unclosed'] = $lang->story->unclosed;
$lang->story->featureBar['browse']['draft'] = $lang->story->statusList['draft'];
$lang->story->featureBar['browse']['reviewing'] = $lang->story->statusList['reviewing'];
$lang->story->operateList = array();
$lang->story->operateList['assigned'] = '指派';
$lang->story->operateList['closed'] = '关闭';
$lang->story->operateList['activated'] = '激活';
$lang->story->operateList['changed'] = '变更';
$lang->story->operateList['reviewed'] = '评审';
$lang->story->operateList['edited'] = '编辑';
$lang->story->operateList['submitreview'] = '提交评审';
$lang->story->operateList['recalledchange'] = '撤销变更';
$lang->story->operateList['recalled'] = '撤销评审';
$lang->requirement->common = $lang->URCommon;
$lang->requirement->create = "提{$lang->URCommon}";
$lang->requirement->batchCreate = "批量创建";
@@ -490,3 +517,7 @@ $lang->requirement->batchAssignTo = "批量指派";
$lang->requirement->batchChangeModule = "批量修改模块";
$lang->requirement->submitReview = $lang->story->submitReview;
$lang->requirement->linkStory = "关联{$lang->SRCommon}";
$lang->story->addBranch = '添加%s';
$lang->story->deleteBranch = '删除%s';
$lang->story->notice->branch = '每个分支会建立一个需求,需求间互为孪生关系,孪生需求间除产品、分支、模块、计划、阶段字段外均同步,后期您可以手动解除孪生关系';
+544 -168
View File
@@ -49,11 +49,18 @@ class storyModel extends model
$story->executions = $this->dao->select('t1.project, t2.name, t2.status, t2.type, t2.multiple')->from(TABLE_PROJECTSTORY)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.project = t2.id')
->where('t1.story')->eq($storyID)
->andWhere('t2.type')->in('sprint,stage,kanban')
->where('t2.type')->in('sprint,stage,kanban')
->beginIF($story->siblings)->andWhere('t1.story')->in(ltrim($story->siblings, ',') . $story->id)
->beginIF(!$story->siblings)->andWhere('t1.story')->in($story->id)
->orderBy('t1.`order` DESC')
->fetchAll('project');
$story->tasks = $this->dao->select('id, name, assignedTo, execution, project, status, consumed, `left`,type')->from(TABLE_TASK)->where('story')->eq($storyID)->andWhere('deleted')->eq(0)->orderBy('id DESC')->fetchGroup('execution');
$story->tasks = $this->dao->select('id, name, assignedTo, execution, project, status, consumed, `left`,type')->from(TABLE_TASK)
->where('deleted')->eq(0)
->beginIF($story->siblings)->andWhere('story')->in(ltrim($story->siblings, ',') . $story->id)
->beginIF(!$story->siblings)->andWhere('story')->in($story->id)
->orderBy('id DESC')
->fetchGroup('execution');
$story->stages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->eq($storyID)->fetchPairs('branch', 'stage');
//$story->bugCount = $this->dao->select('COUNT(*)')->alias('count')->from(TABLE_BUG)->where('story')->eq($storyID)->fetch('count');
//$story->caseCount = $this->dao->select('COUNT(*)')->alias('count')->from(TABLE_CASE)->where('story')->eq($storyID)->fetch('count');
@@ -159,13 +166,20 @@ class storyModel extends model
}
/* Get affected bugs. */
$story->bugs = $this->dao->findByStory($story->id)->from(TABLE_BUG)
$story->bugs = $this->dao->select('*')->from(TABLE_BUG)
->where('status')->ne('closed')
->beginIF($story->siblings)->andWhere('story')->in(ltrim($story->siblings, ',') . $story->id)
->beginIF(!$story->siblings)->andWhere('story')->in($story->id)
->andWhere('status')->ne('closed')
->andWhere('deleted')->eq(0)
->orderBy('id desc')->fetchAll();
/* Get affected cases. */
$story->cases = $this->dao->findByStory($story->id)->from(TABLE_CASE)->andWhere('deleted')->eq(0)->fetchAll();
$story->cases = $this->dao->select('*')->from(TABLE_CASE)
->where('deleted')->eq(0)
->beginIF($story->siblings)->andWhere('story')->in(ltrim($story->siblings, ',') . $story->id)
->beginIF(!$story->siblings)->andWhere('story')->in($story->id)
->fetchAll();
return $story;
}
@@ -228,173 +242,223 @@ class storyModel extends model
->join('assignedTo', '')
->join('mailto', ',')
->stripTags($this->config->story->editor->create['id'], $this->config->allowedTags)
->remove('files,labels,reviewer,needNotReview,newStory,uid,contactListMenu,URS,region,lane,ticket')
->remove('files,labels,reviewer,needNotReview,newStory,uid,contactListMenu,URS,region,lane,ticket,branches,modules,plans')
->get();
/* Check repeat story. */
$result = $this->loadModel('common')->removeDuplicate('story', $story, "product={$story->product}");
if(isset($result['stop']) and $result['stop']) return array('status' => 'exists', 'id' => $result['duplicate']);
if($story->status != 'draft' and $this->checkForceReview()) $story->status = 'reviewing';
if(strpos('draft,reviewing', $story->status) !== false) $story->stage = $this->post->plan > 0 ? 'planned' : 'wait';
$story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->create['id'], $this->post->uid);
$requiredFields = "," . $this->config->story->create->requiredFields . ",";
if($story->type == 'requirement') $requiredFields = str_replace(',plan,', ',', $requiredFields);
if(strpos($requiredFields, ',estimate,') !== false)
$product = $this->loadModel('product')->getById($story->product);
if($product->type == 'normal' or $story->type == 'requirement')
{
if(strlen(trim($story->estimate)) == 0) dao::$errors['estimate'] = sprintf($this->lang->error->notempty, $this->lang->story->estimate);
$requiredFields = str_replace(',estimate,', ',', $requiredFields);
$this->post->branches = isset($story->branch) ? array($story->branch) : array(0 => 0);
$this->post->modules = isset($story->module) ? array($story->module) : array(0 => 0);
$this->post->plans = isset($story->plan) ? array($story->plan) : array(0 => 0);
}
$requiredFields = trim($requiredFields, ',');
$this->dao->insert(TABLE_STORY)->data($story, 'spec,verify')
->autoCheck()
->checkIF($story->notifyEmail, 'notifyEmail', 'email')
->batchCheck($requiredFields, 'notempty')
->checkFlow()
->exec();
if(!dao::isError())
/* check module */
$requiredFields = "," . $this->config->story->create->requiredFields . ",";
if(strpos($requiredFields, ',module,') !== false)
{
$storyID = $this->dao->lastInsertID();
/* Fix bug #21992, user story have no parent story. */
if(isset($story->parent) and $story->parent)
foreach($this->post->modules as $module)
{
$stories = array($storyID);
$this->subdivide($story->parent, $stories);
}
if(!empty($story->plan))
{
$this->updateStoryOrderOfPlan($storyID, $story->plan); // Set story order in this plan.
$this->loadModel('action')->create('productplan', $story->plan, 'linkstory', '', $storyID);
}
$this->file->updateObjectID($this->post->uid, $storyID, $story->type);
$files = $this->file->saveUpload($story->type, $storyID, 1);
$data = new stdclass();
$data->story = $storyID;
$data->version = 1;
$data->title = $story->title;
$data->spec = $story->spec;
$data->verify = $story->verify;
$data->files = join(',', array_keys($files));
$this->dao->insert(TABLE_STORYSPEC)->data($data)->exec();
/* Save the story reviewer to storyreview table. */
if(isset($_POST['reviewer']))
{
foreach($this->post->reviewer as $reviewer)
if(empty($module))
{
if(empty($reviewer)) continue;
$reviewData = new stdclass();
$reviewData->story = $storyID;
$reviewData->version = 1;
$reviewData->reviewer = $reviewer;
$this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
dao::$errors[] = sprintf($this->lang->error->notempty, $this->lang->story->module);
return false;
}
}
}
/* Project or execution linked story. */
if($executionID != 0)
$storyIds = array();
$storyFile = array();
$mainStoryID = 0;
foreach($this->post->branches as $key => $branch)
{
$story->branch = $branch;
$story->module = $this->post->modules[$key];
$story->plan = $this->post->plans[$key];
if(strpos('draft,reviewing', $story->status) !== false) $story->stage = $this->post->plan > 0 ? 'planned' : 'wait';
if($story->type == 'requirement') $requiredFields = str_replace(',plan,', ',', $requiredFields);
if(strpos($requiredFields, ',estimate,') !== false)
{
$this->linkStory($executionID, $this->post->product, $storyID);
if($executionID != $this->session->project) $this->linkStory($this->session->project, $this->post->product, $storyID);
$this->loadModel('kanban');
$laneID = isset($output['laneID']) ? $output['laneID'] : 0;
if(isset($_POST['lane'])) $laneID = $_POST['lane'];
$columnID = $this->kanban->getColumnIDByLaneID($laneID, 'backlog');
if(empty($columnID)) $columnID = isset($output['columnID']) ? $output['columnID'] : 0;
if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'story', $storyID);
if(empty($laneID) or empty($columnID)) $this->kanban->updateLane($executionID, 'story');
if(strlen(trim($story->estimate)) == 0) dao::$errors['estimate'] = sprintf($this->lang->error->notempty, $this->lang->story->estimate);
$requiredFields = str_replace(',estimate,', ',', $requiredFields);
}
if(is_array($this->post->URS))
$requiredFields = trim($requiredFields, ',');
$this->dao->insert(TABLE_STORY)->data($story, 'spec,verify')
->autoCheck()
->checkIF($story->notifyEmail, 'notifyEmail', 'email')
->batchCheck($requiredFields, 'notempty')
->checkFlow()
->exec();
if(dao::isError()) return false;
if(!dao::isError())
{
foreach($this->post->URS as $URID)
$storyID = $this->dao->lastInsertID();
/* Fix bug #21992, user story have no parent story. */
if(isset($story->parent) and $story->parent)
{
$requirement = $this->getByID($URID);
$data = new stdclass();
$data->product = $story->product;
$data->AType = 'requirement';
$data->relation = 'subdivideinto';
$data->BType = 'story';
$data->AID = $URID;
$data->BID = $storyID;
$data->AVersion = $requirement->version;
$data->BVersion = 1;
$data->extra = 1;
$this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec();
$data->AType = 'story';
$data->relation = 'subdividedfrom';
$data->BType = 'requirement';
$data->AID = $storyID;
$data->BID = $URID;
$data->AVersion = 1;
$data->BVersion = $requirement->version;
$this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec();
$stories = array($storyID);
$this->subdivide($story->parent, $stories);
}
}
if($bugID > 0)
{
if(($this->config->edition == 'biz' || $this->config->edition == 'max')) $oldBug = $this->dao->select('feedback, status')->from(TABLE_BUG)->where('id')->eq($bugID)->fetch();
$bug = new stdclass();
$bug->toStory = $storyID;
$bug->status = 'closed';
$bug->resolution = 'tostory';
$bug->resolvedBy = $this->app->user->account;
$bug->resolvedDate = $now;
$bug->closedBy = $this->app->user->account;
$bug->closedDate = $now;
$bug->assignedTo = 'closed';
$bug->assignedDate = $now;
$this->dao->update(TABLE_BUG)->data($bug)->where('id')->eq($bugID)->exec();
$this->loadModel('action')->create('bug', $bugID, 'ToStory', '', $storyID);
$this->action->create('bug', $bugID, 'Closed');
if(($this->config->edition == 'biz' || $this->config->edition == 'max') && !dao::isError() && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, 'closed', $oldBug->status);
/* add files to story from bug. */
$files = $this->dao->select('*')->from(TABLE_FILE)
->where('objectType')->eq('bug')
->andWhere('objectID')->eq($bugID)
->fetchAll();
if(!empty($files))
if(!empty($story->plan))
{
foreach($files as $file)
$this->updateStoryOrderOfPlan($storyID, $story->plan); // Set story order in this plan.
$this->loadModel('action')->create('productplan', $story->plan, 'linkstory', '', $storyID);
}
$this->file->updateObjectID($this->post->uid, $storyID, $story->type);
$files = $this->file->saveUpload($story->type, $storyID, 1);
/* Multi branch sync files */
!empty($files) ? $storyFile = $files : $files = $storyFile;
$data = new stdclass();
$data->story = $storyID;
$data->version = 1;
$data->title = $story->title;
$data->spec = $story->spec;
$data->verify = $story->verify;
$data->files = join(',', array_keys($files));
$this->dao->insert(TABLE_STORYSPEC)->data($data)->exec();
/* Save the story reviewer to storyreview table. */
if(isset($_POST['reviewer']))
{
foreach($this->post->reviewer as $reviewer)
{
$file->objectType = 'story';
$file->objectID = $storyID;
unset($file->id);
$this->dao->insert(TABLE_FILE)->data($file)->exec();
if(empty($reviewer)) continue;
$reviewData = new stdclass();
$reviewData->story = $storyID;
$reviewData->version = 1;
$reviewData->reviewer = $reviewer;
$this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
}
}
/* Project or execution linked story. */
if($executionID != 0)
{
$this->linkStory($executionID, $this->post->product, $storyID);
if($this->config->systemMode == 'new' and $executionID != $this->session->project) $this->linkStory($this->session->project, $this->post->product, $storyID);
$this->loadModel('kanban');
$laneID = isset($output['laneID']) ? $output['laneID'] : 0;
if(isset($_POST['lane'])) $laneID = $_POST['lane'];
$columnID = $this->kanban->getColumnIDByLaneID($laneID, 'backlog');
if(empty($columnID)) $columnID = isset($output['columnID']) ? $output['columnID'] : 0;
if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'story', $storyID);
if(empty($laneID) or empty($columnID)) $this->kanban->updateLane($executionID, 'story');
}
if(is_array($this->post->URS))
{
foreach($this->post->URS as $URID)
{
$requirement = $this->getByID($URID);
$data = new stdclass();
$data->product = $story->product;
$data->AType = 'requirement';
$data->relation = 'subdivideinto';
$data->BType = 'story';
$data->AID = $URID;
$data->BID = $storyID;
$data->AVersion = $requirement->version;
$data->BVersion = 1;
$data->extra = 1;
$this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec();
$data->AType = 'story';
$data->relation = 'subdividedfrom';
$data->BType = 'requirement';
$data->AID = $storyID;
$data->BID = $URID;
$data->AVersion = 1;
$data->BVersion = $requirement->version;
$this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec();
}
}
if($bugID > 0)
{
if(($this->config->edition == 'biz' || $this->config->edition == 'max')) $oldBug = $this->dao->select('feedback, status')->from(TABLE_BUG)->where('id')->eq($bugID)->fetch();
$bug = new stdclass();
$bug->toStory = $storyID;
$bug->status = 'closed';
$bug->resolution = 'tostory';
$bug->resolvedBy = $this->app->user->account;
$bug->resolvedDate = $now;
$bug->closedBy = $this->app->user->account;
$bug->closedDate = $now;
$bug->assignedTo = 'closed';
$bug->assignedDate = $now;
$this->dao->update(TABLE_BUG)->data($bug)->where('id')->eq($bugID)->exec();
$this->loadModel('action')->create('bug', $bugID, 'ToStory', '', $storyID);
$this->action->create('bug', $bugID, 'Closed');
if(($this->config->edition == 'biz' || $this->config->edition == 'max') && !dao::isError() && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, 'closed', $oldBug->status);
/* add files to story from bug. */
$files = $this->dao->select('*')->from(TABLE_FILE)
->where('objectType')->eq('bug')
->andWhere('objectID')->eq($bugID)
->fetchAll();
if(!empty($files))
{
foreach($files as $file)
{
$file->objectType = 'story';
$file->objectID = $storyID;
unset($file->id);
$this->dao->insert(TABLE_FILE)->data($file)->exec();
}
}
}
if(!defined('TUTORIAL')) $this->setStage($storyID);
if(!dao::isError()) $this->loadModel('score')->create('story', 'create',$storyID);
/* Callback the callable method to process the related data for object that is transfered to story. */
if($from && is_callable(array($this, $this->config->story->fromObjects[$from]['callback']))) call_user_func(array($this, $this->config->story->fromObjects[$from]['callback']), $storyID);
$storyIds[] = $storyID;
if(empty($mainStoryID)) $mainStoryID = $storyID;
}
if(!defined('TUTORIAL')) $this->setStage($storyID);
if(!dao::isError()) $this->loadModel('score')->create('story', 'create',$storyID);
/* Callback the callable method to process the related data for object that is transfered to story. */
if($from && is_callable(array($this, $this->config->story->fromObjects[$from]['callback']))) call_user_func(array($this, $this->config->story->fromObjects[$from]['callback']), $storyID);
return array('status' => 'created', 'id' => $storyID);
}
return false;
/* bind siblings story id */
if(count($storyIds) > 1)
{
foreach($storyIds as $siblingsStoryID)
{
$siblingsArr = array();
foreach($storyIds as $idItem)
{
if($idItem != $siblingsStoryID) $siblingsArr[] = $idItem;
$siblings = ',' . implode(',', $siblingsArr) . ',';
$this->dao->update(TABLE_STORY)->set('siblings')->eq($siblings)->where('id')->eq($siblingsStoryID)->exec();
}
}
}
return array('status' => 'created', 'id' => $mainStoryID, 'ids' => $storyIds);
}
/**
@@ -739,6 +803,16 @@ class storyModel extends model
$data->files = $story->files = $addedFiles . trim($storyFiles, ',');
$this->dao->insert(TABLE_STORYSPEC)->data($data)->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$data->story = $siblingID;
$this->dao->insert(TABLE_STORYSPEC)->data($data)->exec();
}
}
/* IF is story and has changed, update its relation version to new. */
if($oldStory->type == 'story')
{
@@ -767,6 +841,16 @@ class storyModel extends model
$reviewData->version = $story->version;
$reviewData->reviewer = $reviewer;
$this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$reviewData->story = $siblingID;
$this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
}
}
}
if($reviewerHasChanged)
@@ -776,7 +860,9 @@ class storyModel extends model
}
}
return common::createChanges($oldStory, $story);
$changes = common::createChanges($oldStory, $story);
if(!empty($oldStory->siblings)) $this->syncSiblings($oldStory->id, $oldStory->siblings, $changes, 'Changed');
return $changes;
}
}
@@ -803,6 +889,17 @@ class storyModel extends model
return false;
}
$storyPlan = !empty($_POST['plan']) ? array_filter($_POST['plan']) : array();
if(count($storyPlan) > 1)
{
$oldStoryPlan = !empty($oldStory->planTitle) ? array_keys($oldStory->planTitle) : array();
if(!empty(array_diff($storyPlan,$oldStoryPlan)) or !empty(array_diff($oldStoryPlan,$storyPlan)))
{
dao::$errors[] = $this->lang->story->notice->changePlan;
return false;
}
}
$story = fixer::input('post')
->cleanInt('product,module,pri,duplicateStory')
->cleanFloat('estimate')
@@ -836,6 +933,14 @@ class storyModel extends model
->remove('files,labels,comment,contactListMenu,stages,reviewer,needNotReview')
->get();
/* Relieve siblings when change product. */
if(!empty($oldStory->siblings) and $story->product != $oldStory->product)
{
$this->dbh->exec("UPDATE " . TABLE_STORY . " SET siblings = REPLACE(siblings, ',$storyID,', ',') WHERE `product` = $oldStory->product");
$this->dao->update(TABLE_STORY)->set('siblings')->eq('')->where('id')->eq($storyID)->orWhere('siblings')->eq(',')->exec();
$oldStory->siblings = '';
}
if($oldStory->type == 'story' and !isset($story->linkStories)) $story->linkStories = '';
if($oldStory->type == 'requirement' and !isset($story->linkRequirements)) $story->linkRequirements = '';
if($oldStory->status == 'changing' and $story->status == 'draft') $story->status = 'changing';
@@ -887,6 +992,19 @@ class storyModel extends model
->beginIF($oldStory->status == 'reviewing')->andWhere('reviewer')->notin(implode(',', $_POST['reviewer']))
->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$this->dao->delete()->from(TABLE_STORYREVIEW)
->where('story')->eq($siblingID)
->andWhere('version')->eq($oldStory->version)
->beginIF($oldStory->status == 'reviewing')->andWhere('reviewer')->notin(implode(',', $_POST['reviewer']))
->exec();
}
}
foreach($_POST['reviewer'] as $reviewer)
{
if($oldStory->status == 'reviewing' and in_array($reviewer, array_keys($oldReviewer))) continue;
@@ -896,6 +1014,16 @@ class storyModel extends model
$reviewData->version = $oldStory->version;
$reviewData->reviewer = $reviewer;
$this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$reviewData->story = $siblingID;
$this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
}
}
}
if($oldStory->status == 'reviewing') $story = $this->updateStoryByReview($storyID, $oldStory, $story);
@@ -933,6 +1061,18 @@ class storyModel extends model
$data->verify = $story->verify;
$data->files = $story->files = $addedFiles . trim($storyFiles, ',');
$this->dao->update(TABLE_STORYSPEC)->data($data)->where('story')->eq((int)$storyID)->andWhere('version')->eq($oldStory->version)->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$this->dao->update(TABLE_STORYSPEC)->data($data)
->where('story')->eq((int)$siblingID)
->andWhere('version')->eq($oldStory->version)
->exec();
}
}
}
if($story->product != $oldStory->product)
@@ -1062,6 +1202,8 @@ class storyModel extends model
if(isset($story->finalResult)) $this->recordReviewAction($story);
}
if(!empty($oldStory->siblings)) $this->syncSiblings($oldStory->id, $oldStory->siblings, $changes, 'Edited');
return true;
}
}
@@ -1475,7 +1617,28 @@ class storyModel extends model
/* Fix bug #671. */
$this->lang->story->closedReason = $this->lang->story->rejectedReason;
$this->dao->update(TABLE_STORYREVIEW)->set('result')->eq($this->post->result)->set('reviewDate')->eq($now)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->eq($this->app->user->account)->exec();
$this->dao->update(TABLE_STORYREVIEW)
->set('result')->eq($this->post->result)
->set('reviewDate')->eq($now)
->where('story')->eq($storyID)
->andWhere('version')->eq($oldStory->version)
->andWhere('reviewer')->eq($this->app->user->account)
->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$this->dao->update(TABLE_STORYREVIEW)
->set('result')->eq($this->post->result)
->set('reviewDate')->eq($now)
->where('story')->eq($siblingID)
->andWhere('version')->eq($oldStory->version)
->andWhere('reviewer')->eq($this->app->user->account)
->exec();
}
}
$story = $this->updateStoryByReview($storyID, $oldStory, $story);
@@ -1507,6 +1670,8 @@ class storyModel extends model
$this->action->logHistory($actionID, $changes);
}
if(!empty($oldStory->siblings)) $this->syncSiblings($oldStory->id, $oldStory->siblings, $changes, 'Reviewed');
return true;
}
@@ -1525,17 +1690,23 @@ class storyModel extends model
$actions = array();
$this->loadModel('action');
$oldStories = $this->getByList($storyIdList);
$hasResult = $this->dao->select('story,version,result')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->andWhere('reviewer')->eq($this->app->user->account)->andWhere('result')->ne('')->orderBy('version')->fetchAll('story');
$reviewerList = $this->dao->select('story,reviewer,result,version')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->orderBy('version')->fetchGroup('story', 'reviewer');
$reviewedSiblings = array();
$oldStories = $this->getByList($storyIdList);
$hasResult = $this->dao->select('story,version,result')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->andWhere('reviewer')->eq($this->app->user->account)->andWhere('result')->ne('')->orderBy('version')->fetchAll('story');
$reviewerList = $this->dao->select('story,reviewer,result,version')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->orderBy('version')->fetchGroup('story', 'reviewer');
foreach($storyIdList as $storyID)
{
if(!$storyID) continue;
$isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ',');
$oldStory = $oldStories[$storyID];
if($oldStory->status != 'reviewing') continue;
foreach($reviewerList[$storyID] as $reviewer => $reviewerInfo)
{
if($reviewerInfo->version != $oldStory->version) unset($reviewerList[$storyID][$reviewer]);
}
if(!in_array($this->app->user->account, array_keys($reviewerList[$storyID])) and $isSuperReviewer === false) continue;
if(isset($hasResult[$storyID]) and $hasResult[$storyID]->version == $oldStories[$storyID]->version) continue;
if($oldStory->version > 1 and $result == 'reject') continue;
@@ -1549,6 +1720,21 @@ class storyModel extends model
$this->dao->update(TABLE_STORYREVIEW)->set('result')->eq($result)->set('reviewDate')->eq($now)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->eq($this->app->user->account)->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$this->dao->update(TABLE_STORYREVIEW)
->set('result')->eq($result)
->set('reviewDate')->eq($now)
->where('story')->eq($siblingID)
->andWhere('version')->eq($oldStory->version)
->andWhere('reviewer')->eq($this->app->user->account)
->exec();
}
}
/* Update the story status by review rules. */
$reviewedBy = explode(',', trim($story->reviewedBy, ','));
if($isSuperReviewer !== false)
@@ -1571,6 +1757,19 @@ class storyModel extends model
$story->id = $storyID;
$story->version = $oldStory->version;
$actions[$storyID] = $this->recordReviewAction($story, $result, $reason);
/* Sync siblings. */
$changes = common::createChanges($oldStory, $story);
if(!empty($oldStory->siblings))
{
$siblings = $oldStory->siblings;
foreach(explode(',', $siblings) as $siblingID)
{
if(in_array($siblingID, $storyIdList) or isset($reviewedSiblings[$siblingID])) $siblings = str_replace(",$siblingID,", ',', $siblings);
}
$this->syncSiblings($storyID, trim($siblings, ','), $changes, 'Reviewed');
foreach(explode(',', trim($siblings, ',')) as $reviewedID) $reviewedSiblings[$reviewedID] = $reviewedID;
}
}
return $actions;
@@ -1585,11 +1784,26 @@ class storyModel extends model
*/
public function recallReview($storyID)
{
$story = $this->getById($storyID);
$isChanged = $story->changedBy ? true : false;
$status = $isChanged ? 'changing' : 'draft';
$this->dao->update(TABLE_STORY)->set('status')->eq($status)->where('id')->eq($storyID)->exec();
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($story->version)->exec();
$oldStory = $this->getById($storyID);
$isChanged = $oldStory->changedBy ? true : false;
$story = clone $oldStory;
$story->status = $isChanged ? 'changing' : 'draft';
$this->dao->update(TABLE_STORY)->set('status')->eq($story->status)->where('id')->eq($storyID)->exec();
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($siblingID)->andWhere('version')->eq($oldStory->version)->exec();
}
}
$changes = common::createChanges($oldStory, $story);
if(!empty($oldStory->siblings)) $this->syncSiblings($storyID, $oldStory->siblings, $changes, 'recalled');
}
/**
@@ -1601,16 +1815,31 @@ class storyModel extends model
*/
public function recallChange($storyID)
{
$story = $this->getById($storyID);
$oldStory = $this->getById($storyID);
/* Update story title and version and status. */
$preVersion = $story->version - 1;
$preTitle = $this->dao->select('title')->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->eq($preVersion)->fetch('title');
$this->dao->update(TABLE_STORY)->set('title')->eq($preTitle)->set('version')->eq($preVersion)->set('status')->eq('active')->where('id')->eq($storyID)->exec();
$story = clone $oldStory;
$story->version = $oldStory->version - 1;
$story->title = $this->dao->select('title')->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->eq($story->version)->fetch('title');
$story->status = 'active';
$this->dao->update(TABLE_STORY)->set('title')->eq($story->title)->set('version')->eq($story->version)->set('status')->eq($story->status)->where('id')->eq($storyID)->exec();
/* Delete versions that is after this version. */
$this->dao->delete()->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->eq($story->version)->exec();
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($story->version)->exec();
$this->dao->delete()->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->eq($oldStory->version)->exec();
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$this->dao->delete()->from(TABLE_STORYSPEC)->where('story')->eq($siblingID)->andWHere('version')->eq($oldStory->version)->exec();
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($siblingID)->andWhere('version')->eq($oldStory->version)->exec();
}
}
$changes = common::createChanges($oldStory, $story);
if(!empty($oldStory->siblings)) $this->syncSiblings($storyID, $oldStory->siblings, $changes, 'recalledChange');
}
/**
@@ -1643,6 +1872,15 @@ class storyModel extends model
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($siblingID)->andWhere('version')->eq($oldStory->version)->exec();
}
}
if(isset($_POST['reviewer']))
{
foreach($this->post->reviewer as $reviewer)
@@ -1654,12 +1892,25 @@ class storyModel extends model
$reviewData->version = $oldStory->version;
$reviewData->reviewer = $reviewer;
$this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$reviewData->story = $siblingID;
$this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
}
}
}
$story->status = 'reviewing';
}
$this->dao->update(TABLE_STORY)->data($story, 'reviewer')->where('id')->eq($storyID)->exec();
if(!dao::isError()) return common::createChanges($oldStory, $story);
$changes = common::createChanges($oldStory, $story);
if(!empty($oldStory->siblings)) $this->syncSiblings($storyID, $oldStory->siblings, $changes, 'submitReview');
if(!dao::isError()) return $changes;
return false;
}
@@ -1797,7 +2048,10 @@ class storyModel extends model
if(($this->config->edition == 'biz' || $this->config->edition == 'max') && $oldStory->feedback) $this->loadModel('feedback')->updateStatus('story', $oldStory->feedback, $story->status, $oldStory->status);
}
return common::createChanges($oldStory, $story);
$changes = common::createChanges($oldStory, $story);
if(!empty($oldStory->siblings)) $this->syncSiblings($storyID, $oldStory->siblings, $changes, 'Closed');
return $changes;
}
/**
@@ -2312,7 +2566,10 @@ class storyModel extends model
$story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->assignto['id'], $this->post->uid);
$this->dao->update(TABLE_STORY)->data($story)->autoCheck()->checkFlow()->where('id')->eq((int)$storyID)->exec();
if(!dao::isError()) return common::createChanges($oldStory, $story);
$changes = common::createChanges($oldStory, $story);
if(!empty($oldStory->siblings)) $this->syncSiblings($storyID, $oldStory->siblings, $changes, 'Assigned');
if(!dao::isError()) return $changes;
return false;
}
@@ -2329,9 +2586,15 @@ class storyModel extends model
$storyIdList = $this->post->storyIdList;
$assignedTo = $this->post->assignedTo;
$oldStories = $this->getByList($storyIdList);
$ignoreStories = '';
foreach($storyIdList as $storyID)
{
$oldStory = $oldStories[$storyID];
if($oldStory->status == 'closed')
{
$ignoreStories .= "#{$storyID},";
continue;
}
if($assignedTo == $oldStory->assignedTo) continue;
$story = new stdclass();
@@ -2344,6 +2607,13 @@ class storyModel extends model
$allChanges[$storyID] = common::createChanges($oldStory, $story);
}
if($ignoreStories)
{
$ignoreStories = trim($ignoreStories, ',');
echo js::alert(sprintf($this->lang->story->ignoreClosedStory, $ignoreStories));
}
return $allChanges;
}
@@ -2381,14 +2651,28 @@ class storyModel extends model
$story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->activate['id'], $this->post->uid);
$this->dao->update(TABLE_STORY)->data($story)->autoCheck()->checkFlow()->where('id')->eq($storyID)->exec();
if($story->status == 'active') $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->exec();
if($story->status == 'active')
{
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($siblingID)->exec();
}
}
}
$this->setStage($storyID);
/* Update parent story status. */
if($oldStory->parent > 0) $this->updateParentStatus($storyID, $oldStory->parent);
return common::createChanges($oldStory, $story);
$changes = common::createChanges($oldStory, $story);
if(!empty($oldStory->siblings)) $this->syncSiblings($storyID, $oldStory->siblings, $changes, 'Activated');
return $changes;
}
/**
@@ -3481,6 +3765,7 @@ class storyModel extends model
->andWhere('status')->notin('closed,draft')
->andWhere('product')->eq($productID)
->andWhere('plan')->in('0,')
->andWhere('siblings')->eq('')
->beginIF($append)->orWhere('id')->in($append)->fi()
->fetchPairs();
return array(0 => '') + $stories ;
@@ -5748,6 +6033,16 @@ class storyModel extends model
/* Delete versions that is after this version. */
$this->dao->delete()->from(TABLE_STORYSPEC)->where('story')->eq($story->id)->andWHere('version')->in($oldStory->version)->exec();
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($story->id)->andWhere('version')->in($oldStory->version)->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$this->dao->delete()->from(TABLE_STORYSPEC)->where('story')->eq($siblingID)->andWHere('version')->in($oldStory->version)->exec();
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($siblingID)->andWhere('version')->in($oldStory->version)->exec();
}
}
}
if($result == 'reject')
@@ -5853,6 +6148,19 @@ class storyModel extends model
->andWhere('result')->eq('')
->exec();
/* Sync siblings. */
if(!empty($oldStory->siblings))
{
foreach(explode(',', trim($oldStory->siblings, ',')) as $siblingID)
{
$this->dao->delete()->from(TABLE_STORYREVIEW)
->where('story')->eq($siblingID)
->andWhere('version')->eq($oldStory->version)
->andWhere('result')->eq('')
->exec();
}
}
return $story;
}
@@ -5980,7 +6288,16 @@ class storyModel extends model
/* Get related objects title or names. */
$relatedSpecs = $this->dao->select('*')->from(TABLE_STORYSPEC)->where('`story`')->in($storyIdList)->orderBy('version desc')->fetchGroup('story');
$fileIdList = array();
foreach($relatedSpecs as $relatedSpec)
{
if(!empty($relatedSpec[0]->files)) $fileIdList[] = $relatedSpec[0]->files;
}
$fileIdList = array_unique($fileIdList);
$relatedFiles = $this->dao->select('id, objectID, pathname, title')->from(TABLE_FILE)->where('objectType')->eq('story')->andWhere('objectID')->in($storyIdList)->andWhere('extra')->ne('editor')->fetchGroup('objectID');
$filesInfo = $this->dao->select('id, objectID, pathname, title')->from(TABLE_FILE)->where('id')->in($fileIdList)->andWhere('extra')->ne('editor')->fetchAll('id');
foreach($stories as $story)
{
$story->spec = '';
@@ -5991,6 +6308,11 @@ class storyModel extends model
$story->title = $storySpec->title;
$story->spec = $storySpec->spec;
$story->verify = $storySpec->verify;
if(!empty($storySpec->files) and empty($relatedFiles[$story->id]) and !empty($filesInfo[$storySpec->files]))
{
$relatedFiles[$story->id][0] = $filesInfo[$storySpec->files];
}
}
if($this->post->fileType == 'csv')
@@ -6079,22 +6401,33 @@ class storyModel extends model
* Get the story status after activation.
*
* @param int $storyID
* @param bool $hasSiblings
* @access public
* @return void
*/
public function getActivateStatus($storyID)
public function getActivateStatus($storyID, $hasSiblings = true)
{
$status = 'active';
$action = $hasSiblings ? 'closed,reviewrejected,syncsiblings' : 'closed,reviewrejected';
$lastRecord = $this->dao->select('action,extra')->from(TABLE_ACTION)
->where('objectType')->eq('story')
->andWhere('objectID')->eq($storyID)
->andWhere('action')->in('closed,reviewrejected')
->andWhere('action')->in($action)
->orderBy('id_desc')
->fetch();
$lastAction = $lastRecord->action;
if($lastAction == 'reviewrejected') $status = 'draft';
if($lastAction == 'closed') $status = strpos($lastRecord->extra, '|') !== false ? substr($lastRecord->extra, strpos($lastRecord->extra, '|') + 1) : 'active';
if(strpos('closed,reviewrejected', $lastAction) !== false)
{
$status = strpos($lastRecord->extra, '|') !== false ? substr($lastRecord->extra, strpos($lastRecord->extra, '|') + 1) : 'active';
}
/* When activating twin story, you need to check the status of the twin story selected when closing. */
if($lastAction == 'syncsiblings')
{
$syncStoryID = strpos($lastRecord->extra, '|') !== false ? substr($lastRecord->extra, strpos($lastRecord->extra, '|') + 1) : 0;
$status = $this->getActivateStatus($syncStoryID, false);
}
return $status;
}
@@ -6133,4 +6466,47 @@ class storyModel extends model
return $lastReviewer;
}
/**
* Sync siblings.
*
* @param int $storyID
* @param string $siblings
* @param array $changes
* @param string $operate
* @access public
* @return void
*/
public function syncSiblings($storyID, $siblings, $changes, $operate)
{
if(empty($siblings) or empty($changes)) return;
/* Get the fields and values to be synchronized. */
$syncFieldList = array();
foreach($changes as $changeInfo)
{
$fieldName = $changeInfo['field'];
$fieldValue = $changeInfo['new'];
if(strpos('product,branch,module,plan,spec,verify,files,reviewers', $fieldName) !== false) continue;
$syncFieldList[$fieldName] = $fieldValue;
}
if(empty($syncFieldList)) return;
/* Synchronize and record dynamics. */
$this->loadModel('action');
$siblings = explode(',', trim($siblings, ','));
foreach($siblings as $siblingID)
{
$this->dao->update(TABLE_STORY)->data($syncFieldList)->where('id')->eq((int)$siblingID)->exec();
if(!dao::isError())
{
$this->setStage($siblingID);
$actionID = $this->action->create('story', $siblingID, 'syncsiblings', '', "$operate|$storyID");
$this->action->logHistory($actionID, $changes);
}
}
}
}
+6
View File
@@ -28,6 +28,12 @@
<th class='w-80px'><?php echo $lang->story->assignedTo;?></th>
<td class='w-p45'><?php echo html::select('assignedTo', $users, $story->closedBy, 'class="form-control chosen"');?></td><td></td>
</tr>
<?php if(!empty($story->siblings)):?>
<tr>
<th></th>
<td colspan='2'><i class='icon-exclamation-sign'></i> <?php echo $lang->story->activateSyncTip;?></td>
</tr>
<?php endif;?>
<?php $this->printExtendFields($story, 'table');?>
<tr>
<th><?php echo $lang->story->comment;?></th>
+7 -1
View File
@@ -28,7 +28,13 @@
<tr>
<th class='w-80px'><?php echo $lang->story->assign;?></th>
<td class='w-p25-f'><?php echo html::select('assignedTo', $users, $story->assignedTo, "class='form-control chosen'");?></td><td></td>
</tr>
</tr>
<?php if(!empty($story->siblings)):?>
<tr>
<th></th>
<td colspan='2'><i class='icon-exclamation-sign'></i> <?php echo $lang->story->assignSyncTip;?></td>
</tr>
<?php endif;?>
<tr class='hide'>
<th><?php echo $lang->story->status;?></th>
<td><?php echo html::hidden('status', $story->status);?></td>
+28
View File
@@ -0,0 +1,28 @@
<?php
$lang->story->currentBranch = sprintf($this->lang->story->currentBranch, $this->lang->product->branchName[$product->type]);
js::set('relieveURL', inlink('ajaxRelieveSibling'));
js::set('storyID', $story->id);
js::set('relieved', $lang->story->relieved);
js::set('relievedTip', $lang->story->relievedTip);
js::set('cancel', $lang->cancel);
$canViewLinkStory = common::hasPriv('story', 'view');
$canRelieved = common::hasPriv('story', 'relieved');
array_unshift($siblings, $story);
$class = isonlybody() ? 'showinonlybody' : 'iframe';
foreach($siblings as $sibling)
{
$id = $sibling->id;
$title = '#' . $id . ' '. $sibling->title;
$branch = $branches[$sibling->branch];
$stage = $lang->story->stageList[$sibling->stage];
$labelClass = $story->branch == $sibling->branch ? 'label-primary' : '';
echo "<li title='$title' class='sibling'>";
echo "<span class='label {$labelClass} label-outline label-badge' title='$branch'>{$branch}</span> ";
echo "<span class='label label-outline' title='{$stage}'>{$stage}</span> ";
echo ($canViewLinkStory ? html::a($this->createLink('story', 'view', "id=$id", '', true), "$title", '', "class='$class viewlink' data-width='80%'") : "$title");
if($canRelieved) echo "<a class='unlink hide' data-id='$id' data-toggle='popover'><i class='icon icon-unlink btn-info'></i></a>";
echo "</li>";
}
?>
+15
View File
@@ -73,6 +73,21 @@
<?php echo $this->fetch('file', 'buildform');?>
</td>
</tr>
<?php if(!empty($siblings)):?>
<tr id='siblingTitle'>
<td colspan='2'>
<?php echo $lang->story->changeSyncTip;?>
<span data-toggle='tooltip' data-placement='right' title='<?php echo $lang->story->syncTip;?>'><i class='icon-help'></i></span>
</td>
</tr>
<tr id='siblingList'>
<td colspan='2'>
<ul class='list-unstyled'>
<?php include './blocksibling.html.php';?>
</ul>
</td>
</tr>
<?php endif;?>
<tr>
<th><?php echo $lang->story->checkAffection;?></th>
<td><?php include './affected.html.php';?></td>
+6
View File
@@ -28,6 +28,12 @@
<th class='thWidth'><?php echo $lang->story->closedReason;?></th>
<td class='w-p25-f'><?php echo html::select('closedReason', $reasonList, '', 'class="form-control" onchange="setStory(this.value)"');?></td><td></td>
</tr>
<?php if(!empty($story->siblings)):?>
<tr>
<th></th>
<td colspan='2'><i class='icon-exclamation-sign'></i> <?php echo $lang->story->closeSyncTip;?></td>
</tr>
<?php endif;?>
<tr id='duplicateStoryBox' style='display:none'>
<th><?php echo $lang->story->duplicateStory;?></th>
<td class='required'><?php echo html::select('duplicateStory', array('' => '') + $productStories, '', "class='form-control' placeholder='{$lang->bug->duplicateTip}'"); ?></td><td></td>
+108 -21
View File
@@ -17,6 +17,9 @@
<?php js::set('feedbackSource', $config->story->feedbackSource); ?>
<?php js::set('storyType', $type);?>
<?php js::set('requiredFields', $config->story->create->requiredFields);?>
<?php if($type == 'requirement'): ?>
<style>.input-group .control-branch + .chosen-container-single .chosen-single {border-radius: 0 2px 2px 0; border-left-width: 0px;}</style>
<?php endif; ?>
<?php
foreach(explode(',', $config->story->create->requiredFields) as $field)
{
@@ -51,35 +54,76 @@ foreach(explode(',', $config->story->create->requiredFields) as $field)
<th><?php echo $hiddenProduct ? $lang->story->module : $lang->story->product;?></th>
<td colspan="2" class="<?php if($hiddenProduct) echo 'hidden';?>">
<div class='input-group'>
<?php echo html::select('product', $products, $productID, "onchange='loadProduct(this.value);' class='form-control chosen control-product'");?>
<?php echo html::select('product', $products, $productID, "onchange='loadProduct(this.value);' class='form-control chosen control-product' required");?>
<span class='input-group-addon fix-border fix-padding'></span>
<?php if($branches) echo html::select('branch', $branches, $branch, "onchange='loadBranch();' class='form-control chosen control-branch'");?>
<?php if($branches and $type != 'story') echo html::select('branch', $branches, $branch, "onchange='loadBranch();' class='form-control chosen control-branch'");?>
</div>
</td>
<td colspan="2">
<div class='input-group' id='moduleIdBox'>
<?php if(!$hiddenProduct):?>
<div class="input-group-addon"><?php echo $lang->story->module;?></div>
<?php endif;?>
<?php
echo html::select('module', $moduleOptionMenu, $moduleID, "class='form-control chosen'");
if(count($moduleOptionMenu) == 1)
{
echo "<div class='input-group-addon'>";
echo html::a($this->createLink('tree', 'browse', "rootID=$productID&view=story&currentModuleID=0&branch=$branch", '', true), $lang->tree->manage, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='90%'");
echo '&nbsp; ';
echo html::a("javascript:void(0)", $lang->refreshIcon, '', "class='refresh' title='refresh' onclick='loadProductModules($productID)'");
echo '</div>';
}
?>
<td colspan='2' id='assignedToBox' class='<?php if((!$branches and $type == 'story') or $type == 'requirement') echo "hidden"; ?> switchBranch'>
<div class='input-group'>
<div class="input-group-addon assignedTo"><?php echo $lang->story->assignedTo;?></div>
<?php echo html::select('assignedTo', $users, '', "class='form-control picker-select'");?>
</div>
</td>
<td class='w-60px <?php if((!$branches and $type == 'story') or $type == 'requirement') echo "hidden"; ?> switchBranch'></td>
<td colspan="2" class='<?php if($branches and $type == 'story') echo "hidden"; ?> switchBranch'>
<div class='input-group' id='moduleIdBox'>
<div class="input-group-addon"><?php echo $lang->story->module;?></div>
<?php
echo html::select('module', $moduleOptionMenu, $moduleID, "class='form-control chosen'");
if(count($moduleOptionMenu) == 1)
{
echo "<div class='input-group-addon'>";
echo html::a($this->createLink('tree', 'browse', "rootID=$productID&view=story&currentModuleID=0&branch=$branch", '', true), $lang->tree->manage, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='90%'");
echo '&nbsp; ';
echo html::a("javascript:void(0)", $lang->refreshIcon, '', "class='refresh' title='refresh' onclick='loadProductModules($productID)'");
echo '</div>';
}
?>
</div>
</td>
</tr>
<?php $hiddenSource = strpos(",$showFields,", ',source,') !== false ? '' : 'hidden';?>
<?php if($type == 'story'):?>
<tr>
<th class="planTh"><?php echo $hiddenPlan ? $lang->story->assignedTo : $lang->story->planAB;?></th>
<td colspan="2" class="<?php if($hiddenPlan) echo 'hidden';?>">
<tr class='<?php if(!$branches) echo 'hidden'; ?> switchBranch' >
<th><?php echo $lang->product->branchName[$product->type];?></th>
<td colspan="4">
<div class='input-group' style='display: flex;'>
<div class='table-col' id='branchBox'>
<div class='input-group'>
<span class='input-group-addon'><?php echo sprintf($lang->product->branch, $lang->product->branchName[$product->type])?></span>
<?php echo html::select('branches[0]', $branches, $branch, "onchange='loadBranchRelation(this.value, 0);' class='form-control chosen control-branch'");?>
</div>
</div>
<div class='table-col' id='moduleIdBox'>
<div class='input-group'>
<span class='input-group-addon fix-border'><?php echo $lang->story->module?></span>
<?php echo html::select('modules[0]', $moduleOptionMenu, $moduleID, "class='form-control chosen'");?>
</div>
</div>
<div class='table-col' id='planIdBox'>
<div class='input-group'>
<span class='input-group-addon fix-border'><?php echo $lang->story->plan;?></span>
<?php echo html::select('plans[0]', $plans, $planID, "class='form-control chosen'");?>
</div>
</div>
</div>
</td>
<td class="table-col c-actions text-left w-60px">
<div>
<a href='javascript:;' onclick='addBranchesBox(this)' class='btn btn-link' title='<?php echo sprintf($lang->story->addBranch, $lang->product->branchName[$product->type])?>'><i class='icon-plus'></i></a>
</div>
</td>
</tr>
<tr class='<?php if(!$branches) echo "hidden"; ?> switchBranch' >
<th></th>
<td colspan="4" id="storyNoticeBranch">
<i class="icon-exclamation-sign"></i> <?php echo $lang->story->notice->branch;?>
</td>
</tr>
<tr class='<?php if($branches) echo "hidden"; ?> switchBranch'>
<th class='planTh'><?php echo $lang->story->planAB;?></th>
<td colspan="2">
<div class='input-group' id='planIdBox'>
<?php
echo html::select('plan', $plans, $planID, "class='form-control chosen'");
@@ -311,10 +355,53 @@ foreach(explode(',', $config->story->create->requiredFields) as $field)
</form>
</div>
</div>
<?php js::set('itemIndex', 1);?>
<?php $i = '%i%';?>
<table class="hidden">
<tr id="addBranchesBox" class="hidden">
<th></th>
<td colspan="4">
<div class='input-group' style='display: flex;'>
<div class='table-col' id='branchBox' style='flex: 1 0 160px'>
<div class='input-group'>
<span class='input-group-addon'><?php echo sprintf($lang->product->branch, $lang->product->branchName[$product->type])?></span>
<?php echo html::select("branches[$i]", $branches, $branch, "onchange='loadBranchRelation(this.value, $i);' class='form-control chosen control-branch'");?>
</div>
</div>
<div class='table-col' id='moduleIdBox' style='flex: 1 0 160px'>
<div class='input-group'>
<span class='input-group-addon fix-border'><?php echo $lang->story->module?></span>
<?php echo html::select("modules[$i]", $moduleOptionMenu, $moduleID, "class='form-control chosen'");?>
</div>
</div>
<div class='table-col' id='planIdBox'>
<div class='input-group'>
<span class='input-group-addon fix-border'><?php echo $lang->story->plan;?></span>
<?php echo html::select("plans[$i]", $plans, $planID, "class='form-control chosen'");?>
</div>
</div>
</div>
</td>
<td class="table-col text-left w-60px c-actions">
<div>
<a href='javascript:;' onclick='addBranchesBox(this)' class='btn btn-link' title='<?php echo sprintf($lang->story->addBranch, $lang->product->branchName[$product->type])?>'><i class='icon-plus'></i></a>
<a href='javascript:;' onclick='deleteBranchesBox(this)' class='btn btn-link' title='<?php echo sprintf($lang->story->deleteBranch, $lang->product->branchName[$product->type])?>'><i class='icon icon-close'></i></a>
</div>
</td>
</tr>
</table>
<?php js::set('executionID', $objectID);?>
<?php js::set('storyModule', $lang->story->module);?>
<?php js::set('storyPlan', $lang->story->plan);?>
<?php js::set('storyType', $type);?>
<?php js::set('originProductType', !empty($branches) ? 'normal' : 'branch');?>
<script>
$(function(){parent.$('body.hide-modal-close').removeClass('hide-modal-close');})
var gap = $('#assignedTo').parent().parent().width();
$('#planIdBox').css('flex', '0 0 ' + gap + 'px')
$("#branches0").parent().parent().css('flex', '1 0 160px')
$("#modules0").parent().parent().css('flex', '1 0 160px')
</script>
<?php include '../../common/view/footer.html.php';?>
+21 -2
View File
@@ -24,6 +24,8 @@
<?php js::set('feedbackSource', $config->story->feedbackSource); ?>
<?php js::set('storyStatus', $story->status);?>
<?php js::set('lastReviewer', explode(',', $lastReviewer))?>
<?php js::set('siblings', $story->siblings)?>
<?php js::Set('relievedSiblingsTip', $lang->story->relievedSiblingsTip)?>
<div class='main-content' id='mainContent'>
<form method='post' enctype='multipart/form-data' target='hiddenwin' id='dataform'>
<div class='main-header'>
@@ -98,6 +100,22 @@
</div>
<?php endif;?>
<?php $this->printExtendFields($story, 'div', 'position=left');?>
<?php if(!empty($siblings)):?>
<div class='detail' id='legendSiblings'>
<div class='detail-title'>
<?php echo $lang->story->changeSyncTip;?>
<span data-toggle='tooltip' data-placement='right' title='<?php echo $lang->story->syncTip;?>'><i class='icon-help'></i></span>
</div>
<div class='form-group'>
<div>
<ul class='list-unstyled'>
<?php $branches = $branchTagOption;?>
<?php include './blocksibling.html.php';?>
</ul>
</div>
</div>
</div>
<?php endif;?>
<div class='detail'>
<div class='detail-title'><?php echo $lang->story->comment;?></div>
<div class='form-group'>
@@ -174,8 +192,9 @@
<th><?php echo $lang->story->plan;?></th>
<td>
<div class='input-group' id='planIdBox'>
<?php $multiple = ($this->session->currentProductType != 'normal' and empty($story->branch)) ? true : false;?>
<?php echo html::select($multiple ? 'plan[]' : 'plan', $plans, $story->plan, "class='form-control chosen'" . ($multiple ? ' multiple' : ''));
<?php $planCount = !empty($story->planTitle) ? count($story->planTitle) : 0?>
<?php $multiple = ($this->session->currentProductType != 'normal' and empty($story->branch) and $planCount > 1) ? 'multiple' : '';?>
<?php echo html::select(!empty($multiple) ? 'plan[]' : 'plan', $plans, $story->plan, "class='form-control chosen' " . $multiple);
if(count($plans) == 1)
{
echo "<span class='input-group-addon'>";
+91 -16
View File
@@ -11,6 +11,17 @@
*/
function loadProduct(productID)
{
if(page == 'edit' && siblings && productID != oldProductID)
{
confirmRelievedSiblings = confirm(relievedSiblingsTip);
if(!confirmRelievedSiblings)
{
$('#product').val(oldProductID);
$('#product').trigger("chosen:updated");
return false;
}
}
if(typeof parentStory != 'undefined' && parentStory)
{
confirmLoadProduct = confirm(moveChildrenTips);
@@ -81,20 +92,81 @@ function loadProductBranches(productID)
if(page == 'create') param = 'active';
$('#branch').remove();
$('#branch_chosen').remove();
$.get(createLink('branch', 'ajaxGetBranches', "productID=" + productID + "&oldBranch=0&param=" + param + "&projectID=" + executionID), function(data)
{
var $product = $('#product');
var $inputGroup = $product.closest('.input-group');
$inputGroup.find('.input-group-addon').toggleClass('hidden', !data);
if(data)
{
$inputGroup.append(data);
$('#branch').css('width', config.currentMethod == 'create' ? '120px' : '65px').chosen();
}
$inputGroup.fixInputGroup();
loadProductModules(productID, $('#branch').val());
loadProductPlans(productID, $('#branch').val());
var isSiblings = storyType == 'story' && page == 'create' ? 'yes' : 'no';
$.get(createLink('branch', 'ajaxGetBranches', "productID=" + productID + "&oldBranch=0&param=" + param + "&projectID=" + executionID + "&withMainBranch=1&isSiblings=" + isSiblings), function(data)
{
if(storyType == 'story' && page == 'create')
{
var newProductType = data ? 'normal' : 'branch';
if(originProductType != newProductType)
{
$('.switchBranch').toggleClass('hidden');
$('.switchBranch').toggleClass('disable');
}
originProductType = newProductType;
$('tr[class^="addBranchesBox"]').remove();
if(data)
{
$.ajaxSettings.async = false;
$.get(createLink('product', 'ajaxGetProductById', "productID=" + productID), function(data)
{
$.cookie('branchSourceName', data.branchSourceName)
$.cookie('branchName', data.branchName)
}, 'json')
$.ajaxSettings.async = true;
gap = $('#product').closest('td').next().width();
$('#planIdBox').css('flex', '0 0 ' + gap + 'px')
$('.switchBranch #branchBox .input-group .input-group-addon').html($.cookie('branchSourceName'))
$('.switchBranch #branchBox').closest('td').prev().html($.cookie('branchName'))
/* reload branch */
$('#branches0').replaceWith(data);
$('#branches0' + "_chosen").remove();
$('#branches0').next('.picker').remove();
$('#branches0').chosen();
loadModuleForSiblings(productID, 0, 0)
loadPlanForSiblings(productID, 0, 0)
/* Init multi branch icon-plus. */
if($(".table-form select[id^='branches']").length == $('.switchBranch #branchBox option').length)
{
$('.table-col .icon-plus').parent().css('pointer-events', 'none')
$('.table-col .icon-plus').parent().addClass('disabled')
}
else
{
$('.table-col .icon-plus').parent().css('pointer-events', 'auto')
$('.table-col .icon-plus').parent().removeClass('disabled')
}
}
else
{
loadProductModules(productID, 0);
loadProductPlans(productID, 0);
}
}
else
{
var $product = $('#product');
var $inputGroup = $product.closest('.input-group');
$inputGroup.find('.input-group-addon').toggleClass('hidden', !data);
if(data)
{
$inputGroup.append(data);
$('#branch').css('width', config.currentMethod == 'create' ? '120px' : '65px').chosen();
}
$inputGroup.fixInputGroup();
loadProductModules(productID, $('#branch').val());
loadProductPlans(productID, $('#branch').val());
}
})
}
@@ -139,9 +211,12 @@ function loadProductPlans(productID, branch)
{
if(typeof(branch) == 'undefined') branch = 0;
if(!branch) branch = 0;
var expired = config.currentMethod == 'create' ? 'unexpired' : '';
planLink = createLink('product', 'ajaxGetPlans', 'productID=' + productID + '&branch=' + branch + '&planID=' + $('#plan').val() + '&fieldID=&needCreate=true&expired='+ expired +'&param=skipParent,' + config.currentMethod);
var $planIdBox = $('#planIdBox');
var param = rawMethod == 'edit' ? 'skipParent|forStory' : 'skipParent';
var expired = config.currentMethod == 'create' ? 'unexpired' : '';
var planLink = createLink('product', 'ajaxGetPlans', 'productID=' + productID + '&branch=' + branch + '&planID=' + $('#plan').val() + '&fieldID=&needCreate=true&expired='+ expired +'&param=skipParent|forStory|' + config.currentMethod);
var $planIdBox = rawMethod == 'create' ? $('.switchBranch #planIdBox') : $('#planIdBox');
$planIdBox.load(planLink, function()
{
$planIdBox.find('#plan').chosen();
+17 -9
View File
@@ -419,17 +419,27 @@
<div class="cell">
<div class='tabs'>
<ul class='nav nav-tabs'>
<?php if(!empty($siblings)):?>
<li class='active'><a href='#legendSiblings' data-toggle='tab'><?php echo $lang->story->siblings;?></a></li>
<?php endif;?>
<?php if($this->config->URAndSR and !$hiddenURS):?>
<li class='active'><a href='#legendStories' data-toggle='tab'><?php echo $story->type == 'story' ? $lang->story->requirement : $lang->story->story;?></a></li>
<li class='<?php if(empty($siblings)) echo 'active';?>'><a href='#legendStories' data-toggle='tab'><?php echo $story->type == 'story' ? $lang->story->requirement : $lang->story->story;?></a></li>
<?php endif;?>
<?php if($story->type == 'story'):?>
<li class="<?php if(!$this->config->URAndSR || $hiddenURS) echo 'active';?>"><a href='#legendProjectAndTask' data-toggle='tab'><?php echo $lang->story->legendProjectAndTask;?></a></li>
<li class="<?php if((!$this->config->URAndSR || $hiddenURS) and empty($siblings)) echo 'active';?>"><a href='#legendProjectAndTask' data-toggle='tab'><?php echo $lang->story->legendProjectAndTask;?></a></li>
<?php endif;?>
<li><a href='#legendRelated' data-toggle='tab'><?php echo $lang->story->legendRelated;?></a></li>
</ul>
<div class='tab-content'>
<?php if(!empty($siblings)):?>
<div class='tab-pane active' id='legendSiblings'>
<ul class="list-unstyled">
<?php include './blocksibling.html.php';?>
</ul>
</div>
<?php endif;?>
<?php if($this->config->URAndSR and !$hiddenURS):?>
<div class='tab-pane active' id='legendStories'>
<div class='tab-pane <?php if(empty($siblings)) echo 'active';?>' id='legendStories'>
<ul class="list-unstyled">
<?php
$relation = array();
@@ -438,19 +448,18 @@
foreach($relations as $item) $relation[$item->id] = $item->title;
foreach($relation as $id => $title)
{
echo "<li title='$title'>" . ($canViewLinkStory ? html::a($this->createLink('story', 'view', "id=$id&version=0&param=0&storyType=$relationType", '', true), "#$id $title", '', "class='iframe' data-width='80%'") : "#$id $title");
echo html::a($this->createLink('story', 'linkStory', "storyID=$story->id&type=remove&linkedID=$id&browseType=&queryID=0&storyType=$story->type"), '<i class="icon icon-close"></i>', 'hiddenwin', "class='hide removeButton'");
echo "<li title='$title' class='legendStories'>" . ($canViewLinkStory ? html::a($this->createLink('story', 'view', "id=$id&version=0&param=0&storyType=$relationType", '', true), "#$id $title", '', "class='iframe' data-width='80%'") : "#$id $title");
echo html::a($this->createLink('story', 'linkStory', "storyID=$story->id&type=remove&linkedID=$id&browseType=&queryID=0&storyType=$story->type"), '<i class="icon icon-unlink btn-info"></i>', 'hiddenwin', "class='hide removeButton'");
}
?>
<?php $linkLang = ($story->type == 'story') ? $lang->story->requirement : $lang->story->story;?>
<li><?php if(common::hasPriv($story->type, 'linkStory')) echo html::a($this->createLink('story', 'linkStory', "storyID=$story->id&type=linkStories&linkedID=0&browseType=&queryID=0&storyType=$story->type", '', true), $lang->story->link . $linkLang, '', "class='btn btn-info iframe' data-width='95%' id='linkButton'");?>
<?php if(!empty($relations)) echo html::a('javascript:void(0)', $lang->story->unlink, '', "class='btn btn-info' id='unlinkStory'");?></li>
</ul>
</div>
<?php endif;?>
<?php if($story->type == 'story'):?>
<div class="tab-pane <?php if(!$this->config->URAndSR || $hiddenURS) echo 'active';?>" id='legendProjectAndTask'>
<div class="tab-pane <?php if((!$this->config->URAndSR || $hiddenURS) and empty($siblings)) echo 'active';?>" id='legendProjectAndTask'>
<ul class="list-unstyled">
<?php
foreach($story->tasks as $executionTasks)
@@ -662,7 +671,6 @@ js::set('unlink', $lang->story->unlink);
js::set('cancel', $lang->cancel);
js::set('rawModule', $this->app->rawModule);
?>
<script>
</script>
<?php include '../../common/view/syntaxhighlighter.html.php';?>
<?php include '../../common/view/footer.html.php';?>
+1 -1
View File
@@ -176,7 +176,7 @@
foreach($modulePath as $key => $module)
{
if($tab == 'qa' || $tab == 'ops')
if($tab == 'qa' || $tab == 'ops' || $tab == 'product')
{
if($isLibCase)
{
+2 -2
View File
@@ -552,13 +552,13 @@ class tree extends control
$changeFunc = '';
if($viewType == 'bug' or $viewType == 'case') $changeFunc = "onchange='loadModuleRelated()'";
if($viewType == 'task') $changeFunc = "onchange='setStories(this.value, $rootID)'";
$field = $fieldID ? "modules[$fieldID]" : 'module';
$field = $fieldID !== '' ? "modules[$fieldID]" : 'module';
$currentModule = $this->tree->getById($currentModuleID);
$currentModuleID = (isset($currentModule->branch) and $currentModule->branch == 0) ? $currentModuleID : 0;
$output = html::select("$field", $optionMenu, $currentModuleID, "class='form-control' $changeFunc");
if(count($optionMenu) == 1 and $needManage)
if(count($optionMenu) == 1 and $needManage !== 'false')
{
$output .= "<span class='input-group-addon'>";
$output .= html::a($this->createLink('tree', 'browse', "rootID=$rootID&view=$viewType&currentModuleID=0&branch=$branch", '', true), $this->lang->tree->manage, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='95%'");
+1 -6
View File
@@ -1044,12 +1044,7 @@ function disableSelectedProduct()
$("select[id^='products']").each(function()
{
var selectedProduct = $(this).val();
if(selectedProduct != 0 && $.inArray(selectedProduct, selectedVal) < 0 && !multiBranchProducts[selectedProduct]) selectedVal.push(selectedProduct);
if(multiBranchProducts[selectedProduct])
{
var isDisabled = checkMultiProducts(this);
if(isDisabled) selectedVal.push(selectedProduct);
}
if(selectedProduct != 0 && $.inArray(selectedProduct, selectedVal) < 0) selectedVal.push(selectedProduct);
})
$("select[id^='products']").each(function()