diff --git a/module/story/config/form.php b/module/story/config/form.php index 3ff460bc82..dd8a7e0ff4 100644 --- a/module/story/config/form.php +++ b/module/story/config/form.php @@ -53,10 +53,21 @@ $config->story->form->batchCreate['reviewer'] = array('type' => 'array', 'co $config->story->form->batchCreate['keywords'] = array('type' => 'string', 'control' => 'text', 'required' => false, 'default' => ''); $config->story->form->change = array(); -$config->story->form->change['reviewer'] = array('type' => 'array', 'control' => 'multi-select', 'required' => true, 'default' => '', 'options' => ''); +$config->story->form->change['reviewer'] = array('type' => 'array', 'control' => 'multi-select', 'required' => true, 'default' => '', 'filter' => 'join', 'options' => ''); $config->story->form->change['title'] = array('type' => 'string', 'control' => 'text', 'required' => true, 'filter' => 'trim'); $config->story->form->change['color'] = array('type' => 'string', 'control' => 'color', 'required' => false, 'default' => ''); $config->story->form->change['spec'] = array('type' => 'string', 'control' => 'editor', 'required' => false, 'default' => ''); $config->story->form->change['verify'] = array('type' => 'string', 'control' => 'editor', 'required' => false, 'default' => ''); $config->story->form->change['status'] = array('type' => 'string', 'control' => 'hidden', 'required' => false, 'default' => ''); $config->story->form->change['lastEditedDate'] = array('type' => 'string', 'control' => 'hidden', 'required' => false, 'default' => ''); + +$config->story->form->review = array(); +$config->story->form->review['reviewedDate'] = array('type' => 'date', 'control' => 'text', 'required' => false, 'default' => ''); +$config->story->form->review['result'] = array('type' => 'string', 'control' => 'select', 'required' => true, 'default' => '', 'options' => '', 'title' => $lang->story->reviewResult); +$config->story->form->review['assignedTo'] = array('type' => 'string', 'control' => 'select', 'required' => false, 'default' => '', 'options' => 'users'); +$config->story->form->review['closedReason'] = array('type' => 'string', 'control' => 'select', 'required' => false, 'default' => '', 'options' => $lang->story->reasonList, 'title' => $lang->story->rejectedReason); +$config->story->form->review['pri'] = array('type' => 'int', 'control' => 'select', 'required' => false, 'default' => '', 'options' => $lang->story->priList); +$config->story->form->review['estimate'] = array('type' => 'float', 'control' => 'text', 'required' => false, 'default' => ''); +$config->story->form->review['duplicateStory'] = array('type' => 'string', 'control' => 'text', 'required' => false, 'default' => ''); +$config->story->form->review['childStories'] = array('type' => 'string', 'control' => 'text', 'required' => false, 'default' => ''); +$config->story->form->review['status'] = array('type' => 'string', 'control' => 'hidden', 'required' => false, 'default' => ''); diff --git a/module/story/control.php b/module/story/control.php index 2d43529915..a3fca5d615 100755 --- a/module/story/control.php +++ b/module/story/control.php @@ -1042,51 +1042,15 @@ class story extends control return print(js::locate($this->createLink($module, $method, $params), 'parent')); } - /* Get story and product. */ - $story = $this->story->getById($storyID); - $product = $this->dao->findById($story->product)->from(TABLE_PRODUCT)->fields('name, id')->fetch(); - - $this->story->replaceURLang($story->type); - - /* Set menu. */ - if($this->app->tab == 'project') - { - $this->loadModel('project')->setMenu($this->session->project); - } - elseif($this->app->tab == 'product') - { - $this->product->setMenu($product->id, $story->branch); - } - elseif($this->app->tab == 'execution') - { - $this->loadModel('execution')->setMenu($this->session->execution); - } - - /* Set the review result options. */ + $this->commonAction($storyID); + $story = $this->view->story; $reviewers = $this->story->getReviewerPairs($storyID, $story->version); - $this->lang->story->resultList = $this->lang->story->reviewResultList; + $this->story->getAffectedScope($story); - if($story->status == 'reviewing') - { - if($story->version == 1) unset($this->lang->story->resultList['revert']); - if($story->version > 1) unset($this->lang->story->resultList['reject']); - } - - $this->view->title = $this->lang->story->review . "STORY" . $this->lang->colon . $story->title; - - $this->view->product = $product; - $this->view->story = $story; - $this->view->actions = $this->action->getList('story', $storyID); - $this->view->users = $this->loadModel('user')->getPairs('nodeleted|noclosed', "$story->lastEditedBy,$story->openedBy"); + $this->view->title = $this->lang->story->review . "STORY" . $this->lang->colon . $story->title; + $this->view->fields = $this->storyZen->getFormFieldsForReview($storyID); $this->view->reviewers = $reviewers; - $this->view->isLastOne = count(array_diff(array_keys($reviewers), explode(',', $story->reviewedBy))) == 1 ? true : false; - - /* Get the affcected things. */ - $this->story->getAffectedScope($this->view->story); - $this->app->loadLang('task'); - $this->app->loadLang('bug'); - $this->app->loadLang('testcase'); - $this->app->loadLang('execution'); + $this->view->isLastOne = count(array_diff(array_keys($reviewers), explode(',', $story->reviewedBy))) <= 1; $this->display(); } diff --git a/module/story/css/change.ui.css b/module/story/css/change.ui.css deleted file mode 100644 index 593b420a9b..0000000000 --- a/module/story/css/change.ui.css +++ /dev/null @@ -1,2 +0,0 @@ -#mainContent .title{font-size:16px; font-weight: bold; padding-top:4px;} -#mainContent .title .label{margin-left:10px; font-weight:normal; margin-top:3px;} diff --git a/module/story/css/common.ui.css b/module/story/css/common.ui.css new file mode 100644 index 0000000000..078b09030f --- /dev/null +++ b/module/story/css/common.ui.css @@ -0,0 +1,3 @@ +#mainContent .form-title{font-size:16px; font-weight: bold; padding-top:4px; background:none;} +#mainContent .form-title .label{margin-left:10px; font-weight:normal; margin-top:3px;} + diff --git a/module/story/js/review.ui.js b/module/story/js/review.ui.js new file mode 100644 index 0000000000..0a22acb36e --- /dev/null +++ b/module/story/js/review.ui.js @@ -0,0 +1,47 @@ +window.switchShow = function(obj) +{ + var result = $(obj).val(); + + $('#priBox').addClass('hidden'); + $('#estimateBox').addClass('hidden'); + if(result == 'reject') + { + $('#rejectedReasonBox').removeClass('hidden'); + $('#assignedToBox').addClass('hidden'); + } + else if(result == 'revert') + { + $('#rejectedReasonBox').addClass('hidden'); + $('#duplicateStoryBox').addClass('hidden'); + $('#childStoriesBox').addClass('hidden'); + if(isLastOne) $('#assignedToBox').removeClass('hidden'); + } + else if(result == 'clarify') + { + $('#rejectedReasonBox').addClass('hidden'); + $('#duplicateStoryBox').addClass('hidden'); + $('#childStoriesBox').addClass('hidden'); + $('#rejectedReasonBox').addClass('hidden'); + if(isLastOne) $('#assignedToBox').removeClass('hidden'); + } + else + { + $('#rejectedReasonBox').addClass('hidden'); + $('#duplicateStoryBox').addClass('hidden'); + $('#childStoriesBox').addClass('hidden'); + $('#rejectedReasonBox').addClass('hidden'); + if(isLastOne) $('#assignedToBox').removeClass('hidden'); + if(result == 'pass') + { + $('#priBox').removeClass('hidden'); + $('#estimateBox').removeClass('hidden'); + } + } +} + +window.setStory = function(obj) +{ + var reason = $(obj).val(); + $('#duplicateStoryBox').toggleClass('hidden', reason != 'duplicate'); + $('#childStoriesBox').toggleClass('hidden', reason != 'subdivided'); +} diff --git a/module/story/model.php b/module/story/model.php index a38a2c31e6..1b74da0c18 100644 --- a/module/story/model.php +++ b/module/story/model.php @@ -162,7 +162,7 @@ class storyModel extends model */ public function getAffectedScope($story) { - $users = $this->loadModel('user')->getPairs('pofirst|nodeleted|noclosed', $story->assignedTo); + $users = $this->loadModel('user')->getPairs('pofirst|nodeleted|noclosed', "{$story->lastEditedBy},{$story->openedBy},{$story->assignedTo}"); $story = $this->storyTao->getAffectedProjects($story, $users); $story = $this->storyTao->getAffectedBugs($story, $users); diff --git a/module/story/ui/change.html.php b/module/story/ui/change.html.php index 1876e32fff..dedd01f76d 100644 --- a/module/story/ui/change.html.php +++ b/module/story/ui/change.html.php @@ -29,7 +29,7 @@ $formTitle = div ), div ( - setClass('form-group title'), + setClass('form-group form-title'), $story->title, span ( diff --git a/module/story/ui/review.html.php b/module/story/ui/review.html.php new file mode 100644 index 0000000000..51696f2559 --- /dev/null +++ b/module/story/ui/review.html.php @@ -0,0 +1,153 @@ + + * @package story + * @link https://www.zentao.net + */ +namespace zin; + +jsVar('storyID', $story->id); +jsVar('storyType', $story->type); +jsVar('rawModule', $this->app->rawModule); +jsVar('isMultiple', count($reviewers) > 1); +jsVar('isLastOne', $isLastOne); + +$formTitle = div +( + span + ( + setClass('form-label'), + $lang->story->reviewAction + ), + div + ( + setClass('form-group form-title'), + $story->title, + span + ( + setClass('label text-gray size-sm'), + $story->id + ) + ), +); + +$formItems = array(); +foreach($fields as $field => $attr) +{ + $width = zget($attr, 'width', '1/3'); + $fieldName = zget($attr, 'name', $field); + $control = array(); + $control['type'] = $attr['control']; + if(!empty($attr['options'])) $control['items'] = $attr['options']; + + $formItems[$field] = formRow + ( + formGroup + ( + set::width($width), + set::name($fieldName), + set::label($attr['title']), + set::control($control), + set::value($attr['default']), + set::required($attr['required']) + ) + ); +} + +$affectedProjects = array(); +$affectedTaskCount = 0; +foreach($story->executions as $executionID => $execution) +{ + $teams = ''; + foreach($story->teams[$executionID] as $member) $teams .= zget($users, $member) . ' '; + $affectedTaskCount += count($story->tasks[$executionID]); + $affectedProjects[] = h6 + ( + $execution->name, + $teams ? small(icon('group'), $teams) : null + ); + $affectedProjects[] = dtable + ( + set::cols($config->story->affect->projects->fields), + set::data(array_values($story->tasks[$executionID])) + ); +} + +$formItems['affected'] = formGroup +( + setClass('w-full'), + set::label($lang->story->checkAffection), + tabs + ( + setClass('w-full'), + tabPane + ( + to::suffix(label($affectedTaskCount)), + set::key('affectedProjects'), + set::title($lang->story->affectedProjects), + set::active(true), + $affectedProjects, + ), + tabPane + ( + to::suffix(label(count($story->bugs))), + set::key('affectedBugs'), + set::title($lang->story->affectedBugs), + empty($story->bugs) ? null : dtable + ( + set::cols($config->story->affect->bugs->fields), + set::data(array_values($story->bugs)) + ) + ), + tabPane + ( + to::suffix(label(count($story->cases))), + set::key('affectedCases'), + set::title($lang->story->affectedCases), + empty($story->cases) ? null : dtable + ( + set::cols($config->story->affect->cases->fields), + set::data(array_values($story->cases)) + ) + ), + empty($story->twins) ? null : tabPane + ( + to::suffix(label(count($story->twins))), + set::key('affectedTwins'), + set::title($lang->story->affectedTwins), + dtable + ( + set::cols($config->story->affect->twins->fields), + set::data(array_values($story->twins)) + ) + ), + ) +); + +$formItems['result']->add(on::change('switchShow(e.target);')); +$formItems['assignedTo']->add(set::id('assignedToBox'))->add(set::hidden(!$isLastOne)); +$formItems['closedReason']->add(set::id('rejectedReasonBox'))->add(set::hidden(true))->add(on::change('setStory(e.target);')); +$formItems['duplicateStory']->add(set::id('duplicateStoryBox'))->add(set::hidden(true)); +$formItems['pri']->add(set::id('priBox'))->add(set::hidden(true)); +$formItems['estimate']->add(set::id('estimateBox'))->add(set::hidden(true)); +$formItems['childStories']->add(set::id('childStoriesBox'))->add(set::hidden(true)); +$formItems['status']->add(set::hidden(true)); + +panel +( + setClass('panel-form mx-auto'), + set::title(''), + form + ( + $formTitle, + $formItems, + ), + h::hr(set::class('mt-6 mb-6')), + history() +); + +render(); diff --git a/module/story/zen.php b/module/story/zen.php index 2a990b2316..70f21bc372 100644 --- a/module/story/zen.php +++ b/module/story/zen.php @@ -435,6 +435,14 @@ class storyZen extends story return $fields; } + /** + * 获取变更需求的表单字段。 + * Get form fields for change story. + * + * @param int $storyID + * @access protected + * @return array + */ protected function getFormFieldsForChange(int $storyID): array { $story = $this->view->story; @@ -466,6 +474,47 @@ class storyZen extends story return $fields; } + /** + * 获取评审需求的表单字段。 + * Get form fields for review story. + * + * @param int $storyID + * @access protected + * @return array + */ + protected function getFormFieldsForReview(int $storyID): array + { + $story = $this->view->story; + $fields = $this->config->story->form->review; + $users = $this->loadModel('user')->getPairs('nodeleted|noclosed', "$story->lastEditedBy,$story->openedBy"); + $resultList = $this->lang->story->reviewResultList; + if($story->status == 'reviewing') + { + if($story->version == 1) unset($resultList['revert']); + if($story->version > 1) unset($resultList['reject']); + } + + foreach($fields as $field => $attr) + { + if(isset($attr['options']) and $attr['options'] == 'users') $fields[$field]['options'] = $users; + if(!isset($fields[$field]['name'])) $fields[$field]['name'] = $field; + if(!isset($fields[$field]['title'])) $fields[$field]['title'] = zget($this->lang->story, $field); + } + $fields['result']['options'] = $resultList; + + $fields['reviewedDate']['default'] = helper::now(); + $fields['assignedTo']['default'] = $story->assignedTo; + $fields['pri']['default'] = $story->pri; + $fields['estimate']['default'] = $story->estimate; + $fields['status']['default'] = $story->status; + + $fields['closedReason']['required'] = true; + $fields['duplicateStory']['required'] = true; + + $fields['comment'] = array('type' => 'string', 'control' => 'editor', 'required' => false, 'default' => '', 'name' => 'comment', 'title' => $this->lang->comment, 'width' => 'full'); + return $fields; + } + /** * 设置模块字段的表单字段。 * Set module form field.