diff --git a/db/update18.0.sql b/db/update18.0.sql
index ac0d4a5e1d..8439d996f6 100644
--- a/db/update18.0.sql
+++ b/db/update18.0.sql
@@ -1 +1 @@
-ALTER TABLE `zt_story` ADD COLUMN `siblings` text NOT NULL AFTER `linkRequirements`;
+ALTER TABLE `zt_story` ADD COLUMN `siblings` varchar(255) NOT NULL AFTER `linkRequirements`;
diff --git a/db/zentao.sql b/db/zentao.sql
index ebb17b239c..ca53cfc01a 100755
--- a/db/zentao.sql
+++ b/db/zentao.sql
@@ -1473,7 +1473,7 @@ CREATE TABLE IF NOT EXISTS `zt_story` (
`childStories` varchar(255) NOT NULL,
`linkStories` varchar(255) NOT NULL,
`linkRequirements` varchar(255) NOT NULL,
- `siblings` text 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,
diff --git a/module/action/lang/de.php b/module/action/lang/de.php
index 273f1eb867..5456d6efac 100644
--- a/module/action/lang/de.php
+++ b/module/action/lang/de.php
@@ -393,6 +393,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';
/* Dynamic information is grouped by object. */
$lang->action->dynamicAction = new stdclass;
diff --git a/module/action/lang/en.php b/module/action/lang/en.php
index b6202e131d..2100dd3d45 100755
--- a/module/action/lang/en.php
+++ b/module/action/lang/en.php
@@ -393,6 +393,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';
/* Dynamic information is grouped by object. */
$lang->action->dynamicAction = new stdclass;
diff --git a/module/action/lang/fr.php b/module/action/lang/fr.php
index e8dd3c9eee..022f63f752 100644
--- a/module/action/lang/fr.php
+++ b/module/action/lang/fr.php
@@ -393,6 +393,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';
/* Dynamic information is grouped by object. */
$lang->action->dynamicAction = new stdclass();
diff --git a/module/action/lang/vi.php b/module/action/lang/vi.php
index 93814e6516..516f9d1f1b 100644
--- a/module/action/lang/vi.php
+++ b/module/action/lang/vi.php
@@ -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';
/* Dynamic information is grouped by object. */
$lang->action->dynamicAction = new stdclass;
diff --git a/module/action/lang/zh-cn.php b/module/action/lang/zh-cn.php
index 406172d195..a2d3f3f446 100755
--- a/module/action/lang/zh-cn.php
+++ b/module/action/lang/zh-cn.php
@@ -393,6 +393,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->dynamicAction = new stdclass();
diff --git a/module/action/model.php b/module/action/model.php
index 1d4df4b1aa..4f78c9b844 100755
--- a/module/action/model.php
+++ b/module/action/model.php
@@ -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);
@@ -946,6 +946,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', '');
diff --git a/module/branch/control.php b/module/branch/control.php
index 6f497f7abb..f10bfb8cbd 100644
--- a/module/branch/control.php
+++ b/module/branch/control.php
@@ -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)'"));
}
diff --git a/module/group/lang/resource.php b/module/group/lang/resource.php
index 6ddb8fbeeb..fe94645e1a 100644
--- a/module/group/lang/resource.php
+++ b/module/group/lang/resource.php
@@ -517,6 +517,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';
@@ -546,6 +547,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';
if($config->URAndSR)
{
diff --git a/module/product/control.php b/module/product/control.php
index e9261dddfc..76a023a307 100644
--- a/module/product/control.php
+++ b/module/product/control.php
@@ -959,6 +959,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.
*
@@ -1063,7 +1079,7 @@ class product extends control
{
$param = strtolower($param);
$plans = $this->loadModel('productplan')->getPairs($productID, $branch, $expired, strpos($param, 'skipparent') !== false);
- $field = $fieldID ? "plans[$fieldID]" : 'plan';
+ $field = $fieldID !== '' ? "plans[$fieldID]" : 'plan';
$multiple = strpos($param, 'multiple') === false ? '' : 'multiple';
$output = html::select($field, $plans, $planID, "class='form-control chosen' $multiple");
diff --git a/module/story/control.php b/module/story/control.php
index 4e5fecc46e..95eef188c1 100644
--- a/module/story/control.php
+++ b/module/story/control.php
@@ -121,6 +121,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())
{
@@ -157,7 +158,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);
if($objectID != 0)
{
@@ -2753,18 +2756,18 @@ class story extends control
{
$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'));
- $siblings = str_replace(",$siblingID", '', $story->siblings);
+ /* batchUnset siblingID from siblings.*/
+ $replaceSql = "UPDATE " . TABLE_STORY . " SET siblings = REPLACE(siblings,',$siblingID,', ',') WHERE `product` = $story->product";
+ $this->dbh->exec($replaceSql);
- /* If siblings == ,$siblingID, update siblings empty.*/
- if(count(explode(',', $siblings)) == 3) $siblings = '';
-
- $this->dao->update(TABLE_STORY)->set('siblings')->eq('')->where('id')->eq($siblingID)->exec();
- $this->dao->update(TABLE_STORY)->set('siblings')->eq($siblings)->where('siblings')->like("%,{$_POST['siblingID']},%")->exec();
+ /* 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', 'data' => $siblings));
+ return $this->send(array('result' => 'success', 'silbingsCount' => count($siblings)-1));
}
}
diff --git a/module/story/css/change.css b/module/story/css/change.css
index e65f78dce1..147c332d4f 100644
--- a/module/story/css/change.css
+++ b/module/story/css/change.css
@@ -5,4 +5,3 @@
.colorpicker {right: 7px; top: 7px; opacity: 1;}
#siblingList td, #siblingTitle td {padding-left: 65px;}
#siblingTitle td {font-weight: 700;}
-#siblingList ul li {padding-top: 5px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;}
diff --git a/module/story/css/common.css b/module/story/css/common.css
index 1eb34136d8..d894447467 100644
--- a/module/story/css/common.css
+++ b/module/story/css/common.css
@@ -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;}
@@ -24,6 +25,8 @@
.popover {border:unset;}
.popover.bottom .arrow{border-bottom-color:#ffffff;}
.popover-icon {float:left;}
-.popover .content {margin-left:20px;overflow: overlay;}
+.popover .content {margin-left:25px;overflow: overlay;}
.hide {display:none !important;}
-.sibling {padding:6px;}
+.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;}
diff --git a/module/story/css/create.css b/module/story/css/create.css
index c9854e8d17..ad21e95319 100644
--- a/module/story/css/create.css
+++ b/module/story/css/create.css
@@ -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;}
\ No newline at end of file
diff --git a/module/story/css/edit.css b/module/story/css/edit.css
index 6d1215a2d6..b53c100326 100644
--- a/module/story/css/edit.css
+++ b/module/story/css/edit.css
@@ -5,4 +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;}
-#siblingList ul li {padding-top:5px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;}
+#legendSiblings .form-group {margin-top: 10px;}
diff --git a/module/story/js/common.js b/module/story/js/common.js
index 79bd8d2224..51c47c3603 100644
--- a/module/story/js/common.js
+++ b/module/story/js/common.js
@@ -109,18 +109,20 @@ $('[data-toggle="popover"]').each(function(item) {
function relieve(index)
{
- $.post(relieveURL, {storyID:storyID, siblingID:index}, function(data){
+ $.post(relieveURL, {siblingID:index}, function(data){
$('[data-id="' + index + '"]').popover('hide');
if(data.result == 'success')
{
- if(data.data.length !== 0) $('[data-id="' + index + '"]').parent('li').remove();
- if(data.data.length == 0 || index == storyID)
+ 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');
diff --git a/module/story/js/create.js b/module/story/js/create.js
index 868dfd6a20..8f28ec71ab 100644
--- a/module/story/js/create.js
+++ b/module/story/js/create.js
@@ -108,3 +108,151 @@ 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)
+ {
+ var item = $('#addBranchesBox').html().replace(/%i%/g, itemIndex);
+ $(obj).closest('tr').after('
' + item + '
');
+ $('#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');
+
+ loadBranchForSiblings($('#product').val(), 0, itemIndex)
+ loadModuleForSiblings($('#product').val(), 0, itemIndex)
+ loadPlanForSiblings($('#product').val(), 0, itemIndex)
+ $('.addBranchesBox' + itemIndex + ' #branchBox .input-group .input-group-addon').html($.cookie('branchSourceName'))
+
+ itemIndex ++;
+ }
+
+ /**
+ * Delete branch box.
+ *
+ * @param obj $obj
+ * @access public
+ * @return void
+ */
+ function deleteBranchesBox(obj)
+ {
+ $(obj).closest('tr').remove();
+ }
+
+ /**
+ * 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;
+
+ loadModuleForSiblings(productID, branch, branchIndex)
+ loadPlanForSiblings(productID, branch, branchIndex)
+}
+
+/**
+ * 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=0¶m=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=¤tModuleID=' + 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("" + storyModule + "" );
+
+ $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(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=' + branchIndex + '&needCreate=false&expired='+ expired +'¶m=skipParent,' + 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("" + storyPlan + "");
+ $planIdBox.fixInputGroup();
+ });
+}
diff --git a/module/story/lang/de.php b/module/story/lang/de.php
index 3fd7cebe11..503a183021 100644
--- a/module/story/lang/de.php
+++ b/module/story/lang/de.php
@@ -77,8 +77,9 @@ $lang->story->order = 'Order';
$lang->story->saveDraft = 'Save as draft';
$lang->story->doNotSubmit = 'Do Not Submit';
$lang->story->currentBranch = 'Current Branch';
-$lang->story->sibling = 'Sibling story';
+$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";
@@ -98,9 +99,14 @@ $lang->story->successToTask = "Converted to task.";
$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->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->id = 'ID';
$lang->story->parent = 'Parent';
@@ -347,6 +353,7 @@ $lang->story->action->subdividestory = array('main' => '$date, aufgeteilt
$lang->story->action->unlinkrelatedstory = array('main' => '$date, Verknüpfung aufgelöst durch $actor von Story $extra.');
$lang->story->action->unlinkchildstory = array('main' => '$date, Verknüpfung aufgelöst durch $actor Story $extra.');
$lang->story->action->recalledchange = array('main' => '$date, Undo changes by \$actor.');
+$lang->story->action->syncsiblings = array('main' => "\$date, the system judges that this story is adjusted synchronously due to the \$operate of twin story \$extra.", 'operate' => 'operateList');
/* Statistical statement. */
$lang->story->report = new stdclass();
@@ -472,6 +479,14 @@ $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->requirement->common = $lang->URCommon;
$lang->requirement->create = 'Create Requirement';
$lang->requirement->batchCreate = "Batch Create";
@@ -496,3 +511,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';
\ No newline at end of file
diff --git a/module/story/lang/en.php b/module/story/lang/en.php
index 2b5b16b79e..7c16cd5314 100644
--- a/module/story/lang/en.php
+++ b/module/story/lang/en.php
@@ -77,8 +77,9 @@ $lang->story->order = 'Order';
$lang->story->saveDraft = 'Save as draft';
$lang->story->doNotSubmit = 'Do Not Submit';
$lang->story->currentBranch = 'Current Branch';
-$lang->story->sibling = 'Sibling story';
+$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}";
@@ -98,9 +99,14 @@ $lang->story->successToTask = "Converted to task.";
$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->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->id = 'ID';
$lang->story->parent = 'Parent';
@@ -347,6 +353,7 @@ $lang->story->action->subdividestory = array('main' => '$date, decomposed
$lang->story->action->unlinkrelatedstory = array('main' => '$date, unlinked by $actor from Story $extra.');
$lang->story->action->unlinkchildstory = array('main' => '$date, unlinked by $actor Decomposed Story $extra.');
$lang->story->action->recalledchange = array('main' => '$date, Undo changes by \$actor.');
+$lang->story->action->syncsiblings = array('main' => "\$date, the system judges that this story is adjusted synchronously due to the \$operate of twin story \$extra.", 'operate' => 'operateList');
/* Statistical statement. */
$lang->story->report = new stdclass();
@@ -472,6 +479,14 @@ $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->requirement->common = $lang->URCommon;
$lang->requirement->create = 'Create Requirement';
$lang->requirement->batchCreate = "Batch Create";
@@ -496,3 +511,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';
\ No newline at end of file
diff --git a/module/story/lang/fr.php b/module/story/lang/fr.php
index d044e700a7..2d8ef45343 100644
--- a/module/story/lang/fr.php
+++ b/module/story/lang/fr.php
@@ -77,8 +77,9 @@ $lang->story->order = 'Order';
$lang->story->saveDraft = 'Save as draft';
$lang->story->doNotSubmit = 'Do Not Submit';
$lang->story->currentBranch = 'Current Branch';
-$lang->story->sibling = 'Sibling story';
+$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";
@@ -98,9 +99,14 @@ $lang->story->successToTask = "Converted to task.";
$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->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->id = 'ID';
$lang->story->parent = 'Parent';
@@ -347,6 +353,7 @@ $lang->story->action->subdividestory = array('main' => '$date, décompos
$lang->story->action->unlinkrelatedstory = array('main' => '$date, dissociée par $actor de la Story $extra.');
$lang->story->action->unlinkchildstory = array('main' => '$date, dissociée par $actor de la sous-Story $extra.');
$lang->story->action->recalledchange = array('main' => '$date, Undo changes by \$actor.');
+$lang->story->action->syncsiblings = array('main' => "\$date, the system judges that this story is adjusted synchronously due to the \$operate of twin story \$extra.", 'operate' => 'operateList');
/* Statistical statement. */
$lang->story->report = new stdclass();
@@ -472,6 +479,14 @@ $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->requirement->common = $lang->URCommon;
$lang->requirement->create = 'Create Requirement';
$lang->requirement->batchCreate = "Batch Create";
@@ -496,3 +511,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';
\ No newline at end of file
diff --git a/module/story/lang/vi.php b/module/story/lang/vi.php
index ee7d4c7fe2..591d98b0ae 100644
--- a/module/story/lang/vi.php
+++ b/module/story/lang/vi.php
@@ -93,8 +93,13 @@ $lang->story->successToTask = "Converted to task.";
$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->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->id = 'ID';
$lang->story->parent = 'Mẹ';
@@ -335,6 +340,7 @@ $lang->story->action->subdividestory = array('main' => '$date, được p
$lang->story->action->unlinkrelatedstory = array('main' => '$date, bị hủy bởi $actor từ Câu chuyện $extra.');
$lang->story->action->unlinkchildstory = array('main' => '$date, bị hủy bởi $actor Câu chuyện được phân rã $extra.');
$lang->story->action->recalledchange = array('main' => '$date, Undo changes by \$actor.');
+$lang->story->action->syncsiblings = array('main' => "\$date, the system judges that this story is adjusted synchronously due to the \$operate of twin story \$extra.", 'operate' => 'operateList');
/* Statistical statement. */
$lang->story->report = new stdclass();
@@ -459,3 +465,11 @@ $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';
diff --git a/module/story/lang/zh-cn.php b/module/story/lang/zh-cn.php
index 44cf6d20c6..4380f85915 100644
--- a/module/story/lang/zh-cn.php
+++ b/module/story/lang/zh-cn.php
@@ -76,9 +76,10 @@ $lang->story->undetermined = '待定';
$lang->story->order = '排序';
$lang->story->saveDraft = '存为草稿';
$lang->story->doNotSubmit = '保存暂不提交';
-$lang->story->currentBranch = '当前分支';
-$lang->story->sibling = '孪生需求';
+$lang->story->currentBranch = '当前%s';
+$lang->story->siblings = '孪生需求';
$lang->story->relieved = '解除';
+$lang->story->relievedSiblings = '解除孪生需求';
$lang->story->editAction = "编辑{$lang->SRCommon}";
$lang->story->changeAction = "变更{$lang->SRCommon}";
@@ -98,9 +99,14 @@ $lang->story->successToTask = '批量转任务成功';
$lang->story->storyRound = '第 %s 轮估算';
$lang->story->float = "『%s』应当是正数,可以是小数。";
$lang->story->saveDraftSuccess = '存为草稿成功';
-$lang->story->changeSyncTip = "该需求的修改会同步到如下的孪生需求";
-$lang->story->syncTip = "孪生需求间除产品、分支 、模块、计划、阶段外均同步,孪生关系解除后不再同步";
-$lang->story->relievedTip = "孪生关系解除后无法恢复,需求的内容不再同步,是否解除?";
+
+$lang->story->changeSyncTip = "该需求的修改会同步到如下的孪生需求";
+$lang->story->syncTip = "孪生需求间除产品、分支 、模块、计划、阶段外均同步,孪生关系解除后不再同步";
+$lang->story->relievedTip = "孪生关系解除后无法恢复,需求的内容不再同步,是否解除?";
+$lang->story->assignSyncTip = "孪生需求均同步修改指派人";
+$lang->story->closeSyncTip = "孪生需求均同步关闭";
+$lang->story->activateSyncTip = "孪生需求均同步激活";
+$lang->story->relievedSiblingsTip = '产品调整后,本需求自动解除孪生关系,需求不再同步,是否保存?';
$lang->story->id = '编号';
$lang->story->parent = '父需求';
@@ -347,6 +353,7 @@ $lang->story->action->subdividestory = array('main' => "\$date, 由 story->action->unlinkrelatedstory = array('main' => "\$date, 由 \$actor 移除相关{$lang->SRCommon} \$extra。");
$lang->story->action->unlinkchildstory = array('main' => "\$date, 由 \$actor 移除细分{$lang->SRCommon} \$extra。");
$lang->story->action->recalledchange = array('main' => "\$date, 由 \$actor 撤销变更。");
+$lang->story->action->syncsiblings = array('main' => "\$date, 系统判断由于孪生需求 \$extra \$operate,本需求同步调整。", 'operate' => 'operateList');
/* 统计报表。*/
$lang->story->report = new stdclass();
@@ -472,6 +479,14 @@ $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->requirement->common = $lang->URCommon;
$lang->requirement->create = "提{$lang->URCommon}";
$lang->requirement->batchCreate = "批量创建";
@@ -496,3 +511,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 = '每个分支会建立一个需求,需求间互为孪生关系,孪生需求间除产品、分支、模块、计划、阶段字段外均同步,后期您可以手动解除孪生关系';
\ No newline at end of file
diff --git a/module/story/model.php b/module/story/model.php
index 1739e53719..5cf8b3f3e1 100644
--- a/module/story/model.php
+++ b/module/story/model.php
@@ -49,8 +49,9 @@ class storyModel extends model
$story->executions = $this->dao->select('t1.project, t2.name, t2.status, t2.type')->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, status, consumed, `left`,type')->from(TABLE_TASK)->where('story')->eq($storyID)->andWhere('deleted')->eq(0)->orderBy('id DESC')->fetchGroup('execution');
@@ -159,13 +160,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;
}
@@ -226,173 +234,220 @@ 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();
+ $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($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(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);
+
+ $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);
}
/**
@@ -736,6 +791,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')
{
@@ -764,6 +829,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)
@@ -773,7 +848,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;
}
}
@@ -833,6 +910,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';
@@ -884,6 +969,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;
@@ -893,6 +991,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);
@@ -930,6 +1038,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)
@@ -1059,6 +1179,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;
}
}
@@ -1472,7 +1594,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);
@@ -1504,6 +1647,8 @@ class storyModel extends model
$this->action->logHistory($actionID, $changes);
}
+ if(!empty($oldStory->siblings)) $this->syncSiblings($oldStory->id, $oldStory->siblings, $changes, 'Reviewed');
+
return true;
}
@@ -1531,8 +1676,13 @@ class storyModel extends model
$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;
@@ -1794,7 +1944,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;
}
/**
@@ -2309,7 +2462,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;
}
@@ -2385,7 +2541,9 @@ class storyModel extends model
/* 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;
}
/**
@@ -5678,6 +5836,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')
@@ -5784,6 +5952,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;
}
@@ -6061,4 +6242,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);
+ }
+ }
+ }
}
diff --git a/module/story/view/activate.html.php b/module/story/view/activate.html.php
index e1876c9dfa..4d6a4059d0 100644
--- a/module/story/view/activate.html.php
+++ b/module/story/view/activate.html.php
@@ -28,6 +28,12 @@
story->assignedTo;?> |
closedBy, 'class="form-control chosen"');?> | |
+ siblings)):?>
+
+ |
+ story->activateSyncTip;?> |
+
+
printExtendFields($story, 'table');?>
| story->comment;?> |
diff --git a/module/story/view/assignto.html.php b/module/story/view/assignto.html.php
index 8db1b45f73..fec93c1c35 100644
--- a/module/story/view/assignto.html.php
+++ b/module/story/view/assignto.html.php
@@ -28,7 +28,13 @@
| story->assign;?> |
assignedTo, "class='form-control chosen'");?> | |
-
+
+ siblings)):?>
+
+ |
+ story->assignSyncTip;?> |
+
+
| story->status;?> |
status);?> |
diff --git a/module/story/view/blocksibling.html.php b/module/story/view/blocksibling.html.php
index def4a3e836..000fe2211f 100644
--- a/module/story/view/blocksibling.html.php
+++ b/module/story/view/blocksibling.html.php
@@ -1,17 +1,14 @@
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');
-
-foreach($siblings as $id => $sibling)
-{
- if($story->branch == $sibling->branch) continue;
- unset($siblings[$id]);
- $siblings[] = $sibling;
-}
+$canRelieved = common::hasPriv('story', 'relieved');
+array_unshift($siblings, $story);
+$class = isonlybody() ? 'showinonlybody' : 'iframe';
foreach($siblings as $sibling)
{
@@ -19,9 +16,9 @@ foreach($siblings as $sibling)
$title = $id . ' '. $sibling->title;
$branch = $story->branch == $sibling->branch ? $lang->story->currentBranch : $branches[$sibling->branch];
echo "";
- echo "{$branch} ";
- echo ($canViewLinkStory ? html::a($this->createLink('story', 'view', "id=$id", '', true), "$title", '', "class='iframe' data-width='80%'") : "$id $title");
- echo "";
+ echo "{$branch} ";
+ echo ($canViewLinkStory ? html::a($this->createLink('story', 'view', "id=$id", '', true), "$title", '', "class='$class viewlink' data-width='80%'") : "$id $title");
+ if($canRelieved) echo "";
echo "";
}
?>
diff --git a/module/story/view/change.html.php b/module/story/view/change.html.php
index d2c7e623f9..a07eb54b9c 100644
--- a/module/story/view/change.html.php
+++ b/module/story/view/change.html.php
@@ -83,12 +83,7 @@
- $sibling):?>
- -
- branch];?>
- title;?>
-
-
+
|
diff --git a/module/story/view/close.html.php b/module/story/view/close.html.php
index 8f09e2a651..aa1b6707f5 100644
--- a/module/story/view/close.html.php
+++ b/module/story/view/close.html.php
@@ -28,6 +28,12 @@
story->closedReason;?> |
| |
+ siblings)):?>
+
+ |
+ story->closeSyncTip;?> |
+
+
| story->duplicateStory;?> |
'') + $productStories, '', "class='form-control' placeholder='{$lang->bug->duplicateTip}'"); ?> | |
diff --git a/module/story/view/create.html.php b/module/story/view/create.html.php
index 856bc23ffe..6e3f024204 100644
--- a/module/story/view/create.html.php
+++ b/module/story/view/create.html.php
@@ -51,31 +51,74 @@ foreach(explode(',', $config->story->create->requiredFields) as $field)
story->product;?> |
-
+
-
+
|
-
- ';
- }
- ?>
+ | switchBranch'>
+
+ |
+ switchBranch'> |
+ switchBranch'>
+ ';
+ }
+ ?>
|
-
+
switchBranch' >
+ | product->branchName[$product->type];?> |
+
+
+ |
+
+
+ |
+
+
+ |
+
+ story->notice->branch;?>
+ |
+
+
| story->planAB;?> |
@@ -301,10 +344,53 @@ foreach(explode(',', $config->story->create->requiredFields) as $field)
+
+
+
story->module);?>
+story->plan);?>
+
diff --git a/module/story/view/edit.html.php b/module/story/view/edit.html.php
index e4a7ce91ab..f0affe9be5 100644
--- a/module/story/view/edit.html.php
+++ b/module/story/view/edit.html.php
@@ -24,6 +24,8 @@
story->feedbackSource); ?>
status);?>
+siblings)?>
+story->relievedSiblingsTip)?>
|