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/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 eb9b306212..5bbe651826 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) { 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/js/create.js b/module/story/js/create.js index 868dfd6a20..23b7231072 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=true&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=true&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 f1369fc64d..14c4e80eb5 100644 --- a/module/story/lang/de.php +++ b/module/story/lang/de.php @@ -508,3 +508,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 21f3941765..4de2c96313 100644 --- a/module/story/lang/en.php +++ b/module/story/lang/en.php @@ -508,3 +508,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 d1532afc18..df1111501f 100644 --- a/module/story/lang/fr.php +++ b/module/story/lang/fr.php @@ -508,3 +508,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/zh-cn.php b/module/story/lang/zh-cn.php index ac70cc2d84..4d522d9645 100644 --- a/module/story/lang/zh-cn.php +++ b/module/story/lang/zh-cn.php @@ -508,3 +508,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 8a4a822fb9..464a76a15a 100644 --- a/module/story/model.php +++ b/module/story/model.php @@ -234,173 +234,209 @@ 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()) + $storyIds = array(); + $mainStoryID = 0; + foreach($this->post->branches as $key => $branch) { - $storyID = $this->dao->lastInsertID(); + $story->branch = $branch; + $story->module = $this->post->modules[$key]; + $story->plan = $this->post->plans[$key]; - /* Fix bug #21992, user story have no parent story. */ - if(isset($story->parent) and $story->parent) + if(strpos('draft,reviewing', $story->status) !== false) $story->stage = $this->post->plan > 0 ? 'planned' : 'wait'; + + $requiredFields = "," . $this->config->story->create->requiredFields . ","; + + if($story->type == 'requirement') $requiredFields = str_replace(',plan,', ',', $requiredFields); + if(strpos($requiredFields, ',estimate,') !== false) { - $stories = array($storyID); - $this->subdivide($story->parent, $stories); + if(strlen(trim($story->estimate)) == 0) dao::$errors['estimate'] = sprintf($this->lang->error->notempty, $this->lang->story->estimate); + $requiredFields = str_replace(',estimate,', ',', $requiredFields); } - if(!empty($story->plan)) + $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()) { - $this->updateStoryOrderOfPlan($storyID, $story->plan); // Set story order in this plan. - $this->loadModel('action')->create('productplan', $story->plan, 'linkstory', '', $storyID); - } + $storyID = $this->dao->lastInsertID(); - $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) + /* Fix bug #21992, user story have no parent story. */ + if(isset($story->parent) and $story->parent) { - if(empty($reviewer)) continue; - - $reviewData = new stdclass(); - $reviewData->story = $storyID; - $reviewData->version = 1; - $reviewData->reviewer = $reviewer; - $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec(); + $stories = array($storyID); + $this->subdivide($story->parent, $stories); } - } - /* 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) + if(!empty($story->plan)) { - $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(); + $this->updateStoryOrderOfPlan($storyID, $story->plan); // Set story order in this plan. + $this->loadModel('action')->create('productplan', $story->plan, 'linkstory', '', $storyID); } - } - 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(); + $this->file->updateObjectID($this->post->uid, $storyID, $story->type); + $files = $this->file->saveUpload($story->type, $storyID, 1); - $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(); + $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(); - $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)) + /* Save the story reviewer to storyreview table. */ + if(isset($_POST['reviewer'])) { - foreach($files as $file) + 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); } /** diff --git a/module/story/view/create.html.php b/module/story/view/create.html.php index 856bc23ffe..b04c50041e 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;?>
- + - +
- -
-
story->module;?>
- "; - echo html::a($this->createLink('tree', 'browse', "rootID=$productID&view=story¤tModuleID=0&branch=$branch", '', true), $lang->tree->manage, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='90%'"); - echo '  '; - echo html::a("javascript:void(0)", $lang->refreshIcon, '', "class='refresh' title='refresh' onclick='loadProductModules($productID)'"); - echo '
'; - } - ?> + switchBranch'> +
+
story->assignedTo;?>
+ +
+ + switchBranch'> + switchBranch'> +
+
story->module;?>
+ "; + echo html::a($this->createLink('tree', 'browse', "rootID=$productID&view=story¤tModuleID=0&branch=$branch", '', true), $lang->tree->manage, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='90%'"); + echo '  '; + echo html::a("javascript:void(0)", $lang->refreshIcon, '', "class='refresh' title='refresh' onclick='loadProductModules($productID)'"); + echo '
'; + } + ?> - + switchBranch' > + product->branchName[$product->type];?> + +
+
+
+ product->branch, $lang->product->branchName[$product->type])?> + +
+
+
+
+ story->module?> + +
+
+
+
+ story->plan;?> + +
+
+
+ + +
+ +
+ + + + + + story->notice->branch;?> + + + story->planAB;?>
@@ -301,10 +344,52 @@ foreach(explode(',', $config->story->create->requiredFields) as $field)
+ + + + + + + + + story->module);?> +story->plan);?> diff --git a/module/story/view/header.html.php b/module/story/view/header.html.php index 5d84b1a6b8..31b5bd2cfd 100644 --- a/module/story/view/header.html.php +++ b/module/story/view/header.html.php @@ -81,20 +81,71 @@ function loadProductBranches(productID) if(page == 'create') param = 'active'; $('#branch').remove(); $('#branch_chosen').remove(); - $.get(createLink('branch', 'ajaxGetBranches', "productID=" + productID + "&oldBranch=0¶m=" + 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' ? 'yes' : 'no'; + $.get(createLink('branch', 'ajaxGetBranches', "productID=" + productID + "&oldBranch=0¶m=" + param + "&projectID=" + executionID + "&withMainBranch=1&isSiblings=" + isSiblings), function(data) + { + if(storyType == 'story') + { + $('.switchBranch').toggleClass('hidden'); + $('.switchBranch').toggleClass('disable'); + $('tr[class^="addBranchesBox"]').remove(); + + if(data) + { + $.get(createLink('product', 'ajaxGetProductById', "productID=" + productID), function(data) + { + $('.switchBranch #branchBox .input-group .input-group-addon').html(data.branchSourceName) + $('.switchBranch #branchBox').closest('td').prev().html(data.branchName) + $.cookie('branchSourceName', data.branchSourceName) + }, 'json') + + /* reload branch */ + $('#branches0').replaceWith(data); + $('#branches0' + "_chosen").remove(); + $('#branches0').next('.picker').remove(); + $('#branches0').chosen(); + + $.get(createLink('tree', 'ajaxGetOptionMenu', 'productID=' + productID + '&viewtype=story&branch=0' + '&rootModuleID=0&returnType=html&fieldID=0' + '&needManage=true&extra=¤tModuleID=0'), function(moduleData) + { + if(moduleData) + { + $('#modules0').replaceWith(moduleData); + $('#modules0' + "_chosen").remove(); + $('#modules0').next('.picker').remove(); + $('#modules0').chosen(); + } + }); + + var expired = config.currentMethod == 'create' ? 'unexpired' : ''; + $.get(createLink('product', 'ajaxGetPlans', 'productID=' + productID + '&branch=0' + '&planID=' + $('#plan').val() + '&fieldID=0' + '&needCreate=true&expired='+ expired +'¶m=skipParent,' + config.currentMethod), function(planData) + { + if(planData) + { + $('#plans0').replaceWith(planData); + $('#plans0' + "_chosen").remove(); + $('#plans0').next('.picker').remove(); + $('#plans0').chosen(); + } + }); + } + } + 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()); + } + }) } @@ -184,4 +235,4 @@ function loadProductReviewers(productID) } }); } - + \ No newline at end of file diff --git a/module/tree/control.php b/module/tree/control.php index 49ac28269b..f245d371b6 100644 --- a/module/tree/control.php +++ b/module/tree/control.php @@ -538,7 +538,7 @@ 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;