diff --git a/config/zentaopms.php b/config/zentaopms.php index 31d0b2a51b..a97a6d47a3 100644 --- a/config/zentaopms.php +++ b/config/zentaopms.php @@ -221,6 +221,7 @@ $config->openMethods[] = 'kanban.finishcard'; $config->openMethods[] = 'kanban.deleteobjectcard'; $config->openMethods[] = 'admin.ignore'; $config->openMethods[] = 'personnel.unbindwhitelist'; +$config->openMethods[] = 'tree.viewhistory'; /* Define the tables. */ define('TABLE_COMPANY', '`' . $config->db->prefix . 'company`'); diff --git a/framework/control.class.php b/framework/control.class.php index 602d69722a..b937a96342 100644 --- a/framework/control.class.php +++ b/framework/control.class.php @@ -312,6 +312,26 @@ class control extends baseControl chdir($currentPWD); } + /** + * 获取一个方法的输出内容,这样我们可以在一个方法里获取其他模块方法的内容。 + * 如果模块名为空,则调用该模块、该方法;如果设置了模块名,调用指定模块指定方法。 + * + * Get the output of one module's one method as a string, thus in one module's method, can fetch other module's content. + * If the module name is empty, then use the current module and method. If set, use the user defined module and method. + * + * @param string $moduleName module name. + * @param string $methodName method name. + * @param array $params params. + * @access public + * @return string the parsed html. + */ + public function fetch($moduleName = '', $methodName = '', $params = array(), $appName = '') + { + if($moduleName != $this->moduleName) $this->app->fetchModule = $moduleName; + + return parent::fetch($moduleName, $methodName, $params, $appName); + } + /** * Build operate menu of a method. * diff --git a/framework/router.class.php b/framework/router.class.php index 456845c4cc..2ce88d9cd5 100755 --- a/framework/router.class.php +++ b/framework/router.class.php @@ -46,6 +46,15 @@ class router extends baseRouter */ public $isFlow = false; + /** + * Fetch的模块名。 + * The fetched module name. + * + * @var string + * @access public + */ + public $fetchModule; + /** * Get the $moduleRoot var. * diff --git a/lib/dao/dao.class.php b/lib/dao/dao.class.php index 537484e1a5..0695c65d0d 100644 --- a/lib/dao/dao.class.php +++ b/lib/dao/dao.class.php @@ -48,7 +48,7 @@ class dao extends baseDAO /* Check current module is buildin workflow. */ if(isset($config->workflow->buildin->modules)) { - $currentModule = $app->rawModule; + $currentModule = $app->fetchModule ? $app->fetchModule : $app->rawModule; foreach($config->workflow->buildin->modules as $appModules) { if(!empty($appModules->$currentModule)) diff --git a/lib/filter/filter.class.php b/lib/filter/filter.class.php index fbf1775f0c..269176ae6f 100644 --- a/lib/filter/filter.class.php +++ b/lib/filter/filter.class.php @@ -51,7 +51,7 @@ class fixer extends baseFixer { global $app, $dbh; $flowFields = array(); - $moduleName = $app->rawModule; + $moduleName = $app->fetchModule ? $app->fetchModule : $app->rawModule; $stmt = $dbh->query("SELECT * FROM " . TABLE_WORKFLOWFIELD . " WHERE `module` = '{$moduleName}' and `buildin` = '0'"); while($flowField = $stmt->fetch()) $flowFields[$flowField->field] = $flowField; diff --git a/module/bug/control.php b/module/bug/control.php index b0c319c502..f4045ed16a 100755 --- a/module/bug/control.php +++ b/module/bug/control.php @@ -395,6 +395,12 @@ class bug extends control { $this->dao->update(TABLE_TODO)->set('status')->eq('done')->where('id')->eq($output['todoID'])->exec(); $this->action->create('todo', $output['todoID'], 'finished', '', "BUG:$bugID"); + + if($this->config->edition == 'biz' || $this->config->edition == 'max') + { + $todo = $this->dao->select('type, idvalue')->from(TABLE_TODO)->where('id')->eq($output['todoID'])->fetch(); + if($todo->type == 'feedback' && $todo->idvalue) $this->loadModel('feedback')->updateStatus('todo', $todo->idvalue, 'done'); + } } $message = $this->executeHooks($bugID); @@ -404,11 +410,12 @@ class bug extends control if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $bugID)); /* If link from no head then reload. */ - if(isset($output['executionID']) and isonlybody()) + if(isonlybody()) { - $executionID = $this->post->execution ? $this->post->execution : $output['executionID']; + $executionID = isset($output['executionID']) ? $output['executionID'] : $this->session->execution; + $executionID = $this->post->execution ? $this->post->execution : $executionID; $execution = $this->loadModel('execution')->getByID($executionID); - if($executionID == $output['executionID'] and $this->app->tab == 'execution' and $execution->type == 'kanban') + if($this->app->tab == 'execution' and $execution->type == 'kanban') { $execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all'; $execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default'; diff --git a/module/dept/model.php b/module/dept/model.php index fb9cf45819..8349d58a4b 100644 --- a/module/dept/model.php +++ b/module/dept/model.php @@ -409,6 +409,7 @@ class deptModel extends model ->where('deleted')->eq(0) ->beginIF(strpos($params, 'all') === false)->andWhere('type')->eq($type)->fi() ->beginIF($childDepts)->andWhere('dept')->in($childDepts)->fi() + ->beginIF($deptID === '0')->andWhere('dept')->eq($deptID)->fi() ->beginIF($this->config->vision)->andWhere("CONCAT(',', visions, ',')")->like("%,{$this->config->vision},%")->fi() ->orderBy('account') ->fetchPairs(); diff --git a/module/execution/control.php b/module/execution/control.php index e81ff05f8b..15d6a14c4a 100644 --- a/module/execution/control.php +++ b/module/execution/control.php @@ -2271,7 +2271,6 @@ class execution extends control public function kanban($executionID, $browseType = 'all', $orderBy = 'id_asc', $groupBy = 'default') { $this->app->loadLang('bug'); - $this->loadModel('story'); if(empty($groupBy)) $groupBy = 'default'; @@ -2345,7 +2344,6 @@ class execution extends control $this->view->kanbanData = $kanbanData; $this->view->executionActions = $executionActions; $this->view->kanban = $this->lang->execution->kanban; - $this->view->reviewStoryParis = $this->story->getExecutionStoryPairs($execution->id, 0, 'all', 0, 'full', 'review'); $this->display(); } @@ -2374,7 +2372,6 @@ class execution extends control /* Load language. */ $this->app->loadLang('task'); $this->app->loadLang('bug'); - $this->loadModel('story'); $this->loadModel('kanban'); /* Compatibility IE8. */ @@ -2435,7 +2432,6 @@ class execution extends control $this->view->groupBy = $groupBy; $this->view->canBeChanged = $canBeChanged; $this->view->userList = $userList; - $this->view->reviewStoryParis = $this->story->getExecutionStoryPairs($execution->id, 0, 'all', 0, 'full', 'review'); $this->display(); } diff --git a/module/execution/css/cfd.css b/module/execution/css/cfd.css index a4b56cf4df..9655023122 100644 --- a/module/execution/css/cfd.css +++ b/module/execution/css/cfd.css @@ -2,20 +2,33 @@ .pull-left .input-control {margin-right: 10px;} +#cfdWrapper {max-width: 1450px; padding: 25px 0px 10px; position: relative} +#cfdYUnit {position: absolute; color: #CBD0DB; top: 40px; left: 30px} +#cfdXUnit {position: absolute; color: #CBD0DB; bottom: 20px; left: 1200px} @media screen and (max-width: 1920px) { -#cfdWrapper {max-width: 1400px; margin: 0 auto; padding: 25px 0px 10px; position: relative} #cfdChart {width:1400px !important; height:600px !important} -#cfdYUnit {position: absolute; color: #CBD0DB; top: 40px; left: 30px} -#cfdXUnit {position: absolute; color: #CBD0DB; bottom: 20px; right: 0px} +#mainContent {min-width: 1550px !important;} +#burnStatistics {left: 1400px !important;} +#cfdXUnit {left: 1400px;} +} +@media screen and (max-width: 1680px) +{ +#cfdChart {width:1150px !important; height:500px !important} +#mainContent {min-width: 1350px !important;} +#burnStatistics {left: 1150px !important;} +#cfdXUnit {left: 1150px;} } @media screen and (max-width: 1366px) { -#cfdChart {width:1150px !important; height:400px !important}; +#cfdChart {width:1000px !important; height:400px !important} +#mainContent {min-width: 1200px !important;} +#burnStatistics {left: 1000px !important;} +#cfdXUnit {left: 1000px;} } #mainContent {min-width: 1400px;} -#burnStatistics {position: absolute; left: 1200px; width: 180px; height: 200px; top: 20%; margin-top: -30px; line-height: 30px; color: #838A9D; font-size: 12px; border: 1px solid #dcdcdc; background-color: #F8F8F8; padding: 15px 20px; color: #3c4353; border-radius: 8px;} +#burnStatistics {position: absolute; width: 180px; left: 1200px; height: 200px; top: 20%; margin-top: -30px; line-height: 30px; color: #838A9D; font-size: 12px; border: 1px solid #dcdcdc; background-color: #F8F8F8; padding: 15px 20px; color: #3c4353; border-radius: 8px;} #burnStatistics .bg-primary {margin-right: 5px;} #burnStatistics .icon-help {color: black;} -#burnStatistics h3 {margin-top: 10px; text-indent: 10px; margin-bottom: 30px;} \ No newline at end of file +#burnStatistics h3 {margin-top: 10px; text-indent: 10px; margin-bottom: 30px;} diff --git a/module/execution/css/create.css b/module/execution/css/create.css index 6a7e4ef15c..56238fb200 100644 --- a/module/execution/css/create.css +++ b/module/execution/css/create.css @@ -30,6 +30,8 @@ #dateRange {vertical-align: top; padding-top: 13px;} #dateRangeOption {vertical-align: top; padding-top: 13px;} +#beginLabel {white-space: nowrap;} +#endLabel {white-space: nowrap;} #copyProjectModal .projectSelect {display: inline-block; margin-left: 10px; width: 20%;} #copyProjectModal .titleBox{position: relative; bottom: 3px; display: inline-block;} -a.chosen-single > div > b {top: -4px !important;} +a.chosen-single > div > b {top: -4px !important;} \ No newline at end of file diff --git a/module/execution/js/kanban.js b/module/execution/js/kanban.js index 079b48cd71..3c48a056e5 100644 --- a/module/execution/js/kanban.js +++ b/module/execution/js/kanban.js @@ -1092,28 +1092,29 @@ function changeCardColType(cardID, fromColID, toColID, fromLaneID, toLaneID, car } else { - if(toColType == 'ready' && typeof(reviewStoryParis[objectID]) != 'undefined') + if(toColType == 'ready') { - bootbox.alert(executionLang.storyDragError); - return false; + $.get(createLink('story', 'ajaxGetInfo', "storyID=" + cardID), function(data) + { + if(data) + { + data = $.parseJSON(data); + if(data.status == 'draft' || data.status == 'changed') + { + bootbox.alert(executionLang.storyDragError); + } + else + { + ajaxMoveCard(objectID, fromColID, toColID, fromLaneID, toLaneID, regionID); + } + } + }); + } + else + { + ajaxMoveCard(objectID, fromColID, toColID, fromLaneID, toLaneID, regionID); } - var link = createLink('kanban', 'ajaxMoveCard', 'cardID=' + objectID + '&fromColID=' + fromColID + '&toColID=' + toColID + '&fromLaneID=' + fromLaneID + '&toLaneID=' + toLaneID + '&execitionID=' + executionID + '&browseType=' + browseType + '&groupBy=' + groupBy + '®ionID=' + regionID+ '&orderBy=' + orderBy ); - $.ajax( - { - method: 'post', - dataType: 'json', - url: link, - success: function(data) - { - data = groupBy == 'default' ? data[regionID] : data[groupBy]; - updateRegion(regionID, data); - }, - error: function(xhr, status, error) - { - showErrorMessager(error || lang.timeout); - } - }); } } @@ -1124,6 +1125,38 @@ function changeCardColType(cardID, fromColID, toColID, fromLaneID, toLaneID, car } } +/** + * AJAX: move card. + * + * @param int $objectID + * @param int $fromColID + * @param int $toColID + * @param int $fromLaneID + * @param int $toLaneID + * @param int $regionID + * @access public + * @return void + */ +function ajaxMoveCard(objectID, fromColID, toColID, fromLaneID, toLaneID, regionID) +{ + var link = createLink('kanban', 'ajaxMoveCard', 'cardID=' + objectID + '&fromColID=' + fromColID + '&toColID=' + toColID + '&fromLaneID=' + fromLaneID + '&toLaneID=' + toLaneID + '&execitionID=' + executionID + '&browseType=' + browseType + '&groupBy=' + groupBy + '®ionID=' + regionID+ '&orderBy=' + orderBy ); + $.ajax( + { + method: 'post', + dataType: 'json', + url: link, + success: function(data) + { + data = groupBy == 'default' ? data[regionID] : data[groupBy]; + updateRegion(regionID, data); + }, + error: function(xhr, status, error) + { + showErrorMessager(error || lang.timeout); + } + }); +} + /** * Delete a card. * diff --git a/module/execution/js/taskkanban.js b/module/execution/js/taskkanban.js index 4b402c6b02..2e5b48c0c7 100644 --- a/module/execution/js/taskkanban.js +++ b/module/execution/js/taskkanban.js @@ -774,28 +774,28 @@ function changeCardColType(cardID, fromColID, toColID, fromLaneID, toLaneID, car } else { - if(toColType == 'ready' && typeof(reviewStoryParis[objectID]) != 'undefined') + if(toColType == 'ready') { - bootbox.alert(executionLang.storyDragError); - return false; - } - - var link = createLink('kanban', 'ajaxMoveCard', 'cardID=' + objectID + '&fromColID=' + fromColID + '&toColID=' + toColID + '&fromLaneID=' + fromLaneID + '&toLaneID=' + toLaneID + '&execitionID=' + executionID + '&browseType=' + browseType + '&groupBy=' + groupBy); - $.get(link, function(data) - { - if(data) + $.get(createLink('story', 'ajaxGetInfo', "storyID=" + cardID), function(data) { - kanbanGroup = $.parseJSON(data); - if(groupBy == 'default') + if(data) { - updateKanban('story', kanbanGroup.story); + data = $.parseJSON(data); + if(data.status == 'draft' || data.status == 'changed') + { + bootbox.alert(executionLang.storyDragError); + } + else + { + ajaxMoveCard(objectID, fromColID, toColID, fromLaneID, toLaneID); + } } - else - { - updateKanban(browseType, kanbanGroup[groupBy]); - } - } - }); + }); + } + else + { + ajaxMoveCard(objectID, fromColID, toColID, fromLaneID, toLaneID); + } } } @@ -806,6 +806,37 @@ function changeCardColType(cardID, fromColID, toColID, fromLaneID, toLaneID, car } } +/** + * AJAX: move card. + * + * @param int $objectID + * @param int $fromColID + * @param int $toColID + * @param int $fromLaneID + * @param int $toLaneID + * @access public + * @return void + */ +function ajaxMoveCard(objectID, fromColID, toColID, fromLaneID, toLaneID) +{ + var link = createLink('kanban', 'ajaxMoveCard', 'cardID=' + objectID + '&fromColID=' + fromColID + '&toColID=' + toColID + '&fromLaneID=' + fromLaneID + '&toLaneID=' + toLaneID + '&execitionID=' + executionID + '&browseType=' + browseType + '&groupBy=' + groupBy); + $.get(link, function(data) + { + if(data) + { + kanbanGroup = $.parseJSON(data); + if(groupBy == 'default') + { + updateKanban('story', kanbanGroup.story); + } + else + { + updateKanban(browseType, kanbanGroup[groupBy]); + } + } + }); +} + /** * Handle drop task. * diff --git a/module/execution/view/kanban.html.php b/module/execution/view/kanban.html.php index df3d0fa098..f7855a1d88 100644 --- a/module/execution/view/kanban.html.php +++ b/module/execution/view/kanban.html.php @@ -53,7 +53,6 @@ js::set('projectID', $projectID); js::set('vision', $this->config->vision); js::set('productCount', count($productNames)); js::set('executionID', $execution->id); -js::set('reviewStoryParis', $reviewStoryParis); js::set('needLinkProducts', $lang->execution->needLinkProducts); js::set('lastUpdateData', ''); js::set('rdSearchValue', ''); diff --git a/module/execution/view/taskkanban.html.php b/module/execution/view/taskkanban.html.php index eb3eb87cf8..cee060ec2b 100644 --- a/module/execution/view/taskkanban.html.php +++ b/module/execution/view/taskkanban.html.php @@ -218,7 +218,6 @@ js::set('priv', fluidBoard);?> displayCards);?> - execution->needLinkProducts);?> hourUnit);?> diff --git a/module/gitlab/control.php b/module/gitlab/control.php index 564f183c93..157c5c99a4 100644 --- a/module/gitlab/control.php +++ b/module/gitlab/control.php @@ -634,12 +634,19 @@ class gitlab extends control return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browseUser', "gitlabID=$gitlabID"))); } - $userPairs = $this->loadModel('user')->getPairs('noclosed|noletter'); - $user = $this->gitlab->apiGetSingleUser($gitlabID, $userID); - $zentaoBindAccount = $this->dao->select('account')->from(TABLE_OAUTH)->where('providerType')->eq('gitlab')->andWhere('providerID')->eq($gitlabID)->andWhere('openID')->eq($user->id)->fetch('account'); + $gitlabUser = $this->gitlab->apiGetSingleUser($gitlabID, $userID); + $zentaoBindAccount = $this->dao->select('account')->from(TABLE_OAUTH)->where('providerType')->eq('gitlab')->andWhere('providerID')->eq($gitlabID)->andWhere('openID')->eq($gitlabUser->id)->fetch('account'); + + $users = $this->loadModel('user')->getList(); + $bindedUsers = $this->gitlab->getUserAccountIdPairs($gitlabID); + $userPairs = array('' => ''); + foreach($users as $user) + { + if(!isset($bindedUsers[$user->account]) or $u->account == $zentaoBindAccount) $userPairs[$user->account] = $user->realname; + } $this->view->title = $this->lang->gitlab->common . $this->lang->colon . $this->lang->gitlab->user->edit; - $this->view->user = $user; + $this->view->user = $gitlabUser; $this->view->userPairs = $userPairs; $this->view->zentaoBindAccount = $zentaoBindAccount; $this->view->gitlabID = $gitlabID; diff --git a/module/gitlab/view/browsegroup.html.php b/module/gitlab/view/browsegroup.html.php index cb0a0e07f6..fb4315ff18 100644 --- a/module/gitlab/view/browsegroup.html.php +++ b/module/gitlab/view/browsegroup.html.php @@ -14,7 +14,7 @@