From e523f6390fe0c1a59dadfac2deb61a4787076743 Mon Sep 17 00:00:00 2001 From: lanzongjun Date: Wed, 2 Nov 2022 16:02:42 +0800 Subject: [PATCH] * task #74142 --- module/story/control.php | 191 +++++++++---------------- module/story/model.php | 297 +++++++++++++++++++++------------------ 2 files changed, 226 insertions(+), 262 deletions(-) diff --git a/module/story/control.php b/module/story/control.php index dfcce1efc3..335c3bdb20 100644 --- a/module/story/control.php +++ b/module/story/control.php @@ -122,56 +122,89 @@ class story extends control setcookie('lastStoryModule', (int)$this->post->module, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, false); - if($this->post->branches and $this->post->modules) + $storyResult = $this->story->create($objectID, $bugID, $from = isset($fromObjectIDKey) ? $fromObjectIDKey : '', $extra); + if(!$storyResult or dao::isError()) { - foreach($this->post->branches as $key => $postBranch) + $response['result'] = 'fail'; + $response['message'] = dao::getError(); + return $this->send($response); + } + + $storyID = $storyResult['id']; + $productID = $this->post->product ? $this->post->product : $productID; + + if($storyResult['status'] == 'exists') + { + $response['message'] = sprintf($this->lang->duplicate, $this->lang->story->common); + if($objectID == 0) { - if(empty($this->post->modules[$key])) - { - $response['result'] = 'fail'; - $response['message'] = array(sprintf($this->lang->error->notempty, $this->lang->story->branch)); - return $this->send($response); - } + $response['locate'] = $this->createLink('story', 'view', "storyID={$storyID}&version=0¶m=0&storyType=$storyType"); } - - $storyIds = array(); - $checkDuplicate = 'yes'; - foreach($this->post->branches as $key => $postBranchID) + else { - $postModuleID = $this->post->modules[$key]; - $postPlanID = $this->post->plans[$key]; - - $_POST['branch'] = $postBranchID; - $_POST['module'] = $postModuleID; - $_POST['plan'] = $postPlanID; - - $extra .= ",checkDuplicate=$checkDuplicate,"; - $storyID = $this->postStory($objectID, $bugID, $productID, $todoID, $storyType, $fromObjectIDKey = isset($fromObjectIDKey) ? $fromObjectIDKey : '', $fromObjectAction = isset($fromObjectAction) ? $fromObjectAction: '', $extra); - $checkDuplicate = 'no'; - $storyIds[] = $storyID; + $execution = $this->dao->findById((int)$objectID)->from(TABLE_EXECUTION)->fetch(); + $moduleName = $execution->type == 'project' ? 'projectstory' : 'execution'; + $param = $execution->type == 'project' ? "projectID=$objectID&productID=$productID" : "executionID=$objectID"; + $response['locate'] = $this->createLink($moduleName, 'story', $param); } + return $this->send($response); + } - /* bind siblings story id */ - if(count($storyIds) > 1) + /* bind siblings story id */ + $storyIDs = $storyResult['ids']; + if(count($storyIDs) > 1) + { + foreach($storyIDs as $siblingsStoryID) { - foreach($storyIds as $siblingsStoryID) + $siblingsArr = array(); + foreach($storyIDs as $idItem) { - - $siblingsArr = array(); - foreach($storyIds as $id) - { - if($id != $siblingsStoryID) $siblingsArr[] = $id; - } - + if($idItem != $siblingsStoryID) $siblingsArr[] = $idItem; $siblings = ',' . implode(',', $siblingsArr) . ','; $this->dao->update(TABLE_STORY)->set('siblings')->eq($siblings)->where('id')->eq($siblingsStoryID)->exec(); } } - } - else + + $action = $bugID == 0 ? 'Opened' : 'Frombug'; + $extra = $bugID == 0 ? '' : $bugID; + /* Record related action, for example FromFeedback. */ + if(isset($fromObjectID)) { - $this->postStory($objectID, $bugID, $productID, $todoID, $storyType, $fromObjectIDKey = isset($fromObjectIDKey) ? $fromObjectIDKey : '', $fromObjectAction = isset($fromObjectAction) ? $fromObjectAction: '', $extra); + $action = $fromObjectAction; + $extra = $fromObjectID; + } + /* Create actions. */ + foreach($storyIDs as $idItem) $actionID = $this->action->create('task', $idItem, $action, '', $extra); + + + + if($objectID != 0) + { + $object = $this->dao->findById((int)$objectID)->from(TABLE_PROJECT)->fetch(); + if($object->type != 'project') + { + if($this->config->systemMode == 'new') $this->action->create('story', $storyID, 'linked2project', '', $object->project); + + $actionType = $object->type == 'kanban' ? 'linked2kanban' : 'linked2execution'; + $this->action->create('story', $storyID, $actionType, '', $objectID); + } + else + { + $this->action->create('story', $storyID, 'linked2project', '', $objectID); + } + } + + if($todoID > 0) + { + $this->dao->update(TABLE_TODO)->set('status')->eq('done')->where('id')->eq($todoID)->exec(); + $this->action->create('todo', $todoID, 'finished', '', "STORY:$storyID"); + + if($this->config->edition == 'biz' || $this->config->edition == 'max') + { + $todo = $this->dao->select('type, idvalue')->from(TABLE_TODO)->where('id')->eq($todoID)->fetch(); + if($todo->type == 'feedback' && $todo->idvalue) $this->loadModel('feedback')->updateStatus('todo', $todo->idvalue, 'done'); + } } $message = $this->executeHooks($storyID); @@ -419,92 +452,6 @@ class story extends control $this->display(); } - /** - * post a story. - * - * @param int $objectID - * @param int $bugID - * @param int $productID - * @param int $todoID - * @param string $storyType requirement|story - * @param string $fromObjectIDKey - * @param string $fromObjectAction - * @param string $extra for example feedbackID=0 - * @param string $storyType requirement|story - * @access public - * @return void - */ - private function postStory($objectID, $bugID, $productID, $todoID, $storyType, $fromObjectIDKey, $fromObjectAction, $extra) - { - $storyResult = $this->story->create($objectID, $bugID, $from = isset($fromObjectIDKey) ? $fromObjectIDKey : '', $extra); - if(!$storyResult or dao::isError()) - { - $response['result'] = 'fail'; - $response['message'] = dao::getError(); - return $this->send($response); - } - - $storyID = $storyResult['id']; - $productID = $this->post->product ? $this->post->product : $productID; - - if($storyResult['status'] == 'exists') - { - $response['message'] = sprintf($this->lang->duplicate, $this->lang->story->common); - if($objectID == 0) - { - $response['locate'] = $this->createLink('story', 'view', "storyID={$storyID}&version=0¶m=0&storyType=$storyType"); - } - else - { - $execution = $this->dao->findById((int)$objectID)->from(TABLE_EXECUTION)->fetch(); - $moduleName = $execution->type == 'project' ? 'projectstory' : 'execution'; - $param = $execution->type == 'project' ? "projectID=$objectID&productID=$productID" : "executionID=$objectID"; - $response['locate'] = $this->createLink($moduleName, 'story', $param); - } - return $this->send($response); - } - - $action = $bugID == 0 ? 'Opened' : 'Frombug'; - $extra = $bugID == 0 ? '' : $bugID; - /* Record related action, for example FromFeedback. */ - if(isset($fromObjectID)) - { - $action = $fromObjectAction; - $extra = $fromObjectID; - } - $actionID = $this->action->create('story', $storyID, $action, '', $extra); - - if($objectID != 0) - { - $object = $this->dao->findById((int)$objectID)->from(TABLE_PROJECT)->fetch(); - if($object->type != 'project') - { - if($this->config->systemMode == 'new') $this->action->create('story', $storyID, 'linked2project', '', $object->project); - - $actionType = $object->type == 'kanban' ? 'linked2kanban' : 'linked2execution'; - $this->action->create('story', $storyID, $actionType, '', $objectID); - } - else - { - $this->action->create('story', $storyID, 'linked2project', '', $objectID); - } - } - - if($todoID > 0) - { - $this->dao->update(TABLE_TODO)->set('status')->eq('done')->where('id')->eq($todoID)->exec(); - $this->action->create('todo', $todoID, 'finished', '', "STORY:$storyID"); - - if($this->config->edition == 'biz' || $this->config->edition == 'max') - { - $todo = $this->dao->select('type, idvalue')->from(TABLE_TODO)->where('id')->eq($todoID)->fetch(); - if($todo->type == 'feedback' && $todo->idvalue) $this->loadModel('feedback')->updateStatus('todo', $todo->idvalue, 'done'); - } - } - - return $storyID; - } - /** * Create a batch stories. * diff --git a/module/story/model.php b/module/story/model.php index 668579945e..3d78dad7a1 100644 --- a/module/story/model.php +++ b/module/story/model.php @@ -229,173 +229,190 @@ class storyModel extends model ->remove('files,labels,reviewer,needNotReview,newStory,uid,contactListMenu,URS,region,lane,ticket,branches,modules,plans') ->get(); - if(!empty($output['checkDuplicate']) and $output['checkDuplicate'] == 'yes') - { - /* 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']); - } - + /* 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 = array(isset($story->branch) ? $story->branch : 0); + $this->post->modules = array(isset($story->module) ? $story->module : 0); + $this->post->plans = array(isset($story->plan) ? $story->plan : 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 => $postBranch) { - $storyID = $this->dao->lastInsertID(); + $story->branch = $postBranch; + $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; + return array('status' => 'created', 'id' => $mainStoryID, 'ids' => $storyIds); } /**