diff --git a/db/update15.0.sql b/db/update15.0.sql index 4a1c55ec3c..193e250ca4 100644 --- a/db/update15.0.sql +++ b/db/update15.0.sql @@ -26,3 +26,5 @@ ALTER TABLE `zt_testtask` ADD `testreport` mediumint(8) unsigned NOT NULL AFTER ALTER TABLE `zt_story` MODIFY COLUMN `reviewedDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00'; REPLACE INTO `zt_config` (`owner`, `module`, `section`, `key`, `value`) VALUES ('system', 'story', '', 'reviewRules', 'allpass'); + +UPDATE zt_project SET lifetime = '' WHERE lifetime = 'sprint'; diff --git a/module/action/model.php b/module/action/model.php index 4d00876581..09eb78dbf3 100755 --- a/module/action/model.php +++ b/module/action/model.php @@ -182,34 +182,10 @@ class actionModel extends model return $relation; } - /* Only process these object types. */ if(strpos($this->config->action->needGetRelateField, ",{$objectType},") !== false) { if(!isset($this->config->objectTables[$objectType])) return $emptyRecord; - if($objectType == 'story' and ($actionType == 'linked2build' or $actionType == 'unlinkedfrombuild')) - { - $build = $this->dao->select('project,execution')->from(TABLE_BUILD)->where('id')->eq((int)$extra)->fetch(); - $action->project = $build->project; - $action->execution = $build->execution; - } - - if($objectType == 'story' and $actionType == 'estimated') - { - $action->project = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq((int)$extra)->fetch('project'); - $action->execution = (int)$extra; - } - - if($objectType == 'case' and (strpos(',linked2testtask,unlinkedfromtesttask,assigned,run,', ',' . $actionType . ',') !== false) and (int)$extra) - { - $testtask = $this->dao->select('project,execution')->from(TABLE_TESTTASK)->where('id')->eq((int)$extra)->fetch(); - $action->project = $testtask->project; - $action->execution = $testtask->execution; - } - - if($objectType == 'whitelist' and $extra == 'product') $action->product = $objectID; - if($objectType == 'whitelist' and $extra == 'project') $action->project = $objectID; - if($objectType == 'whitelist' and ($extra == 'sprint' or $extra == 'stage')) $action->execution = $objectID; /* Set fields to fetch. */ $fields = '*'; @@ -244,6 +220,30 @@ class actionModel extends model $record->product = join(',', array_keys($products)); } } + + if($objectType == 'story' and ($actionType == 'linked2build' or $actionType == 'unlinkedfrombuild')) + { + $build = $this->dao->select('project,execution')->from(TABLE_BUILD)->where('id')->eq((int)$extra)->fetch(); + $record->project = $build->project; + $record->execution = $build->execution; + } + + if($objectType == 'story' and $actionType == 'estimated') + { + $record->project = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq((int)$extra)->fetch('project'); + $record->execution = (int)$extra; + } + + if($objectType == 'case' and (strpos(',linked2testtask,unlinkedfromtesttask,assigned,run,', ',' . $actionType . ',') !== false) and (int)$extra) + { + $testtask = $this->dao->select('project,execution')->from(TABLE_TESTTASK)->where('id')->eq((int)$extra)->fetch(); + $record->project = $testtask->project; + $record->execution = $testtask->execution; + } + + if($objectType == 'whitelist' and $extra == 'product') $record->product = $objectID; + if($objectType == 'whitelist' and $extra == 'project') $record->project = $objectID; + if($objectType == 'whitelist' and ($extra == 'sprint' or $extra == 'stage')) $record->execution = $objectID; if($actionType == 'unlinkedfromproject' or $actionType == 'linked2project') $record->project = (int)$extra ; if($actionType == 'unlinkedfromexecution' or $actionType == 'linked2execution') $record->execution = (int)$extra; diff --git a/module/project/view/edit.html.php b/module/project/view/edit.html.php index 7858ee7a3b..8c52ae4ae2 100644 --- a/module/project/view/edit.html.php +++ b/module/project/view/edit.html.php @@ -160,7 +160,7 @@ echo html::hidden('model', $project->model); echo html::submitButton(); $browseLink = $this->session->projectList ? $this->session->projectList : $this->createLink('project', 'browse'); - echo isonlybody() ? html::backButton() : html::a($browseLink, $lang->goback, '', 'class="btn btn-back btn-wide"'); + if(!isonlybody()) echo html::a($browseLink, $lang->goback, '', 'class="btn btn-back btn-wide"'); ?> diff --git a/module/story/control.php b/module/story/control.php index 7a47d54b07..41b308aef2 100644 --- a/module/story/control.php +++ b/module/story/control.php @@ -610,7 +610,8 @@ class story extends control $users = $this->user->getPairs('pofirst|nodeleted|noclosed', "$story->assignedTo,$story->openedBy,$story->closedBy"); $isShowReviewer = true; - $reviewerList = $this->dao->select('reviewer')->from(TABLE_STORYREVIEW)->where('story')->eq($story->id)->andWhere('version')->eq($story->version)->fetchPairs('reviewer'); + $reviewerList = $this->story->getReviewerPairs($story->id, $story->version); + $reviewerList = array_keys($reviewerList); $reviewedBy = explode(',', trim($story->reviewedBy, ',')); if(!array_diff($reviewerList, $reviewedBy)) $isShowReviewer = false; @@ -835,7 +836,7 @@ class story extends control $this->app->loadLang('execution'); $story = $this->story->getById($storyID); - $reviewer = $this->dao->select('reviewer')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($story->version)->fetchAll('reviewer'); + $reviewer = $this->story->getReviewerPairs($storyID, $story->version); /* Assign. */ $this->view->title = $this->lang->story->change . "STORY" . $this->lang->colon . $this->view->story->title; @@ -908,7 +909,7 @@ class story extends control $modulePath = $this->tree->getParents($story->module); $storyModule = empty($story->module) ? '' : $this->tree->getById($story->module); $users = $this->user->getPairs('noletter'); - $reviewers = $this->dao->select('reviewer,result')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($story->version)->fetchAll('reviewer'); + $reviewers = $this->story->getReviewerPairs($storyID, $story->version); /* Set the menu. */ $from = $this->app->openApp; @@ -1012,15 +1013,8 @@ class story extends control if($changes) { - $result = $this->post->result; - $reasonParam = $result == 'reject' ? ',' . $this->post->closedReason : ''; - $story = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); - $reviewers = $this->dao->select('reviewer,result')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($story->version)->fetchAll('reviewer'); - $reviewedBy = explode(',', trim($story->reviewedBy, ',')); - $actionID = $this->action->create('story', $storyID, 'Reviewed', $this->post->comment, ucfirst($result) . $reasonParam); - if($story->status == 'closed') $actionID = $this->action->create('story', $storyID, 'ReviewClosed'); - if($story->status == 'active') $actionID = $this->action->create('story', $storyID, 'PassReviewed'); - if(!array_diff(array_keys($reviewers), $reviewedBy) and ($story->status == 'draft' || $story->status == 'changed')) $actionID = $this->action->create('story', $storyID, 'ClarifyReviewed'); + $story = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + $actionID = $this->story->recordReviewAction($story, $this->post->result, $this->post->closedReason); $this->action->logHistory($actionID, $changes); } @@ -1028,7 +1022,6 @@ class story extends control if(isonlybody()) die(js::reload('parent.parent')); - $module = $from == 'project' ? 'projectstory' : 'story'; die(js::locate($this->createLink($module, 'view', "storyID=$storyID"), 'parent')); } @@ -1055,10 +1048,13 @@ class story extends control } /* Set the review result options. */ - $reviewers = $this->dao->select('reviewer')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($story->version)->fetchAll('reviewer'); + $reviewers = $this->story->getReviewerPairs($storyID, $story->version); $this->lang->story->resultList = $this->lang->story->reviewResultList; + if($story->status == 'draft' and $story->version == 1) unset($this->lang->story->resultList['revert']); + if($story->status == 'changed') unset($this->lang->story->resultList['reject']); + if(count($reviewers) > 1) unset($this->lang->story->resultList['revert']); $this->view->title = $this->lang->story->review . "STORY" . $this->lang->colon . $story->title; diff --git a/module/story/model.php b/module/story/model.php index 8deb4be27d..d121dc3b2c 100644 --- a/module/story/model.php +++ b/module/story/model.php @@ -409,8 +409,8 @@ class storyModel extends model $story->category = $stories->category[$i]; $story->pri = $stories->pri[$i]; $story->estimate = $stories->estimate[$i]; - $story->status = ($this->app->openApp == project || $this->app->openApp == execution ||($stories->needReview[$i] == 0 and !$forceReview)) ? 'active' : 'draft'; - $story->stage = ($this->app->openApp == project || $this->app->openApp == execution) ? 'projected' : 'wait'; + $story->status = ($this->app->openApp == project or $this->app->openApp == execution or ($stories->needReview[$i] == 0 and !$forceReview)) ? 'active' : 'draft'; + $story->stage = ($this->app->openApp == project or $this->app->openApp == execution) ? 'projected' : 'wait'; $story->keywords = $stories->keywords[$i]; $story->sourceNote = $stories->sourceNote[$i]; $story->product = $productID; @@ -633,14 +633,14 @@ class storyModel extends model unset($story->spec); unset($oldStory->spec); - $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->notin(implode(',', $_POST['reviewer']))->exec(); + if(isset($_POST['reviewer'])) $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->notin(implode(',', $_POST['reviewer']))->exec(); } /* Update the reviewer. */ - $oldReviewerList = $this->dao->select('reviewer')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->fetchPairs('reviewer'); + $oldReviewerList = $this->getReviewerPairs($storyID, $oldStory->version); foreach($_POST['reviewer'] as $reviewer) { - if(!$specChanged and in_array($reviewer, $oldReviewerList)) continue; + if(!$specChanged and in_array($reviewer, array_keys($oldReviewerList))) continue; $reviewData = new stdclass(); $reviewData->story = $storyID; @@ -808,17 +808,16 @@ class storyModel extends model if(empty($oldStory->plan) or empty($story->plan)) $this->setStage($storyID); // Set new stage for this story. } - $_POST['reviewer'] = array_filter($_POST['reviewer']); - $oldReviewer = $this->dao->select('reviewer')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->fetchAll('reviewer'); + $_POST['reviewer'] = array_filter($_POST['reviewer']); + $oldReviewer = $this->getReviewerPairs($storyID, $oldStory->version); $oldStory->reviewers = implode(',', array_keys($oldReviewer)); $story->reviewers = implode(',', $_POST['reviewer']); /* Update story reviewer. */ $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->notin(implode(',', $_POST['reviewer']))->exec(); - $existedReviewer = $this->dao->select('reviewer')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->fetchPairs('reviewer'); foreach($_POST['reviewer'] as $reviewer) { - if(in_array($reviewer, $existedReviewer)) continue; + if(in_array($reviewer, array_keys($oldReviewer))) continue; $reviewData = new stdclass(); $reviewData->story = $storyID; @@ -1192,6 +1191,7 @@ class storyModel extends model $story = fixer::input('post') ->setDefault('lastEditedBy', $this->app->user->account) ->setDefault('lastEditedDate', $now) + ->setDefault('status', $oldStory->status) ->setIF($this->post->result == 'revert', 'version', $this->post->preVersion) ->setIF($this->post->result == 'revert', 'status', 'active') ->removeIF($this->post->result != 'reject', 'closedReason, duplicateStory, childStories') @@ -1208,11 +1208,12 @@ class storyModel extends model $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(); /* Update the story status by review rules. */ - $reviewerList = $this->dao->select('reviewer,result')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->fetchPairs('reviewer', 'result'); - $reviewedBy = explode(',', trim($story->reviewedBy, ',')); + $reviewerList = $this->getReviewerPairs($storyID, $oldStory->version); + $reviewedBy = explode(',', trim($story->reviewedBy, ',')); if(!array_diff(array_keys($reviewerList), $reviewedBy)) { - $story->status = $this->setStatusByReviewRules($reviewerList); + $status = $this->setStatusByReviewRules($reviewerList); + $story->status = $status ? $status : $oldStory->status; if($story->status == 'closed') { $story->closedBy = $this->app->user->account; @@ -1261,29 +1262,35 @@ class storyModel extends model $actions = array(); $this->loadModel('action'); - $oldStories = $this->getByList($storyIdList); + $oldStories = $this->getByList($storyIdList); + $hasResult = $this->dao->select('story,version,result')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->andWhere('reviewer')->eq($this->app->user->account)->andWhere('result')->ne('')->orderBy('version')->fetchAll('story'); + $reviewerList = $this->dao->select('story,reviewer,result,version')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->orderBy('version')->fetchGroup('story', 'reviewer'); foreach($storyIdList as $storyID) { $oldStory = $oldStories[$storyID]; if($oldStory->status != 'draft' and $oldStory->status != 'changed') continue; - $hasResult = $this->dao->select('result')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->eq($this->app->user->account)->fetch('result'); - if($hasResult) continue; + if(isset($hasResult[$storyID])) continue; $story = new stdClass(); $story->reviewedDate = $now; $story->lastEditedBy = $this->app->user->account; $story->lastEditedDate = $now; $story->reviewedBy = $oldStory->reviewedBy . ',' . $this->app->user->account; + $story->status = $oldStory->status; $this->dao->update(TABLE_STORYREVIEW)->set('result')->eq($result)->set('reviewDate')->eq($now)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->eq($this->app->user->account)->exec(); /* Update the story status by review rules. */ - $reviewerList = $this->dao->select('reviewer,result')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->fetchPairs('reviewer', 'result'); - $reviewedBy = explode(',', trim($story->reviewedBy, ',')); - if(!array_diff(array_keys($reviewerList), $reviewedBy)) + $reviewedBy = explode(',', trim($story->reviewedBy, ',')); + if(!array_diff(array_keys($reviewerList[$storyID]), $reviewedBy)) { - $status = $this->setStatusByReviewRules($reviewerList); + $reviewerPairs = array(); + foreach($reviewerList[$storyID] as $reviewer => $reviewInfo) $reviewerPairs[$reviewer] = $reviewInfo->result; + $reviewerPairs[$this->app->user->account] = $result; + + $status = $this->setStatusByReviewRules($reviewerPairs); $story->status = $status ? $status : $oldStory->status; + if($story->status == 'closed') { $story->closedBy = $this->app->user->account; @@ -1297,14 +1304,9 @@ class storyModel extends model $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq($storyID)->exec(); $this->setStage($storyID); - $reasonParam = $result == 'reject' ? ',' . $reason : ''; - $reviewers = $this->dao->select('reviewer,result')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->fetchAll('reviewer'); - $reviewedBy = explode(',', trim($story->reviewedBy, ',')); - $actions[$storyID] = $this->action->create('story', $storyID, 'Reviewed', '', ucfirst($result) . $reasonParam); - - if($story->status == 'closed') $this->action->create('story', $storyID, 'ReviewClosed'); - if($story->status == 'active') $this->action->create('story', $storyID, 'PassReviewed'); - if(!array_diff(array_keys($reviewers), $reviewedBy) and ($story->status == 'draft' || $story->status == 'changed')) $this->action->create('story', $storyID, 'ClarifyReviewed'); + $story->id = $storyID; + $story->version = $oldStory->version; + $actions[$storyID] = $this->recordReviewAction($story, $result, $reason); } return $actions; @@ -4220,6 +4222,19 @@ class storyModel extends model } } + /** + * Get story reviewer pairs. + * + * @param int $storyID + * @param int version + * @access public + * @return array + */ + public function getReviewerPairs($storyID, $version) + { + return $this->dao->select('reviewer,result')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($version)->fetchPairs('reviewer', 'result'); + } + /** * Set story status by review rules. * @@ -4229,6 +4244,7 @@ class storyModel extends model */ public function setStatusByReviewRules($reviewerList) { + $status = ''; $passCount = 0; $rejectCount = 0; $reviewRule = $this->config->story->reviewRules; @@ -4252,4 +4268,30 @@ class storyModel extends model return $status; } + + /** + * Record story review actions. + * + * @param object $story + * @param string $result + * @param string $reason + * @access public + * @return int + */ + public function recordReviewAction($story, $result, $reason) + { + $reasonParam = $result == 'reject' ? ',' . $reason : ''; + $reviewers = $this->getReviewerPairs($story->id, $story->version); + $reviewedBy = explode(',', trim($story->reviewedBy, ',')); + + $comment = isset($_POST['comment']) ? $this->post->comment : ''; + $actionID = $this->loadModel('action')->create('story', $story->id, 'Reviewed', $comment, ucfirst($result) . $reasonParam); + + if($story->status == 'closed') $this->action->create('story', $story->id, 'ReviewClosed'); + if($story->status == 'active') $this->action->create('story', $story->id, 'PassReviewed'); + if(!array_diff(array_keys($reviewers), $reviewedBy) and ($story->status == 'draft' || $story->status == 'changed')) $this->action->create('story', $story->id, 'ClarifyReviewed'); + + return $actionID; + } + } diff --git a/module/story/view/view.html.php b/module/story/view/view.html.php index 0304e64e90..2f38ee0ae5 100644 --- a/module/story/view/view.html.php +++ b/module/story/view/view.html.php @@ -501,7 +501,7 @@ id $case->title'>" . html::a($this->createLink('testcase', 'view', "caseID=$case->id", '', true), "[C] #$case->id $case->title") . ''; + echo "