diff --git a/db/update16.2.sql b/db/update16.2.sql index 1939846fa6..b83636466a 100644 --- a/db/update16.2.sql +++ b/db/update16.2.sql @@ -1,12 +1,12 @@ ALTER TABLE `zt_kanbanspace` ADD `type` varchar(50) NOT NULL AFTER `name`; -UPDATE `zt_kanbanspace` SET `type` = 'cooperation'; +UPDATE `zt_kanbanspace` SET `type` = 'cooperation', `whitelist` = ''; -ALTER TABLE `zt_kanban` ADD `importObject` varchar(255) NOT NULL AFTER `displayCards`; +ALTER TABLE `zt_kanban` ADD `object` varchar(255) NOT NULL AFTER `displayCards`; ALTER TABLE `zt_kanban` ADD `performable` enum ('0', '1') NOT NULL DEFAULT '0' AFTER `archived`; ALTER TABLE `zt_kanbancard` ADD `status` varchar(30) NOT NULL AFTER `name`; -ALTER TABLE `zt_kanbancard` ADD `fromID` int(8) NOT NULL AFTER `group`; -ALTER TABLE `zt_kanbancard` ADD `fromType` varchar(50) NOT NULL AFTER `fromID`; +ALTER TABLE `zt_kanbancard` ADD `fromID` mediumint(8) unsigned NOT NULL AFTER `group`; +ALTER TABLE `zt_kanbancard` ADD `fromType` varchar(30) NOT NULL AFTER `fromID`; ALTER TABLE `zt_job` ADD `sonarqubeServer` mediumint(8) unsigned NOT NULL AFTER `triggerType`; -ALTER TABLE `zt_job` ADD `projectKey` varchar(255) NOT NULL AFTER `sonarqubeServer`; +ALTER TABLE `zt_job` ADD `projectKey` varchar(255) NOT NULL AFTER `sonarqubeServer`; \ No newline at end of file diff --git a/db/zentao.sql b/db/zentao.sql index 0726ec6bd9..9fcaf3a999 100644 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -679,7 +679,7 @@ CREATE TABLE `zt_kanban` ( `status` enum('active','closed') NOT NULL default 'active', `order` mediumint(8) NOT NULL DEFAULT '0', `displayCards` smallint(6) NOT NULL default '0', - `importObject` varchar(255) NOT NULL, + `object` varchar(255) NOT NULL, `createdBy` char(30) NOT NULL, `createdDate` datetime NOT NULL, `lastEditedBy` char(30) NOT NULL, @@ -709,6 +709,8 @@ CREATE TABLE `zt_kanbancard` ( `kanban` mediumint(8) unsigned NOT NULL, `region` mediumint(8) unsigned NOT NULL, `group` mediumint(8) unsigned NOT NULL, + `fromID` mediumint(8) undigned NOT NULL, + `fromType` varchar(30) NOT NULL, `name` varchar(255) NOT NULL, `pri` mediumint(8) unsigned NOT NULL, `assignedTo` text NOT NULL, diff --git a/module/action/lang/en.php b/module/action/lang/en.php index 4c1e7b54e7..fc2d284de4 100755 --- a/module/action/lang/en.php +++ b/module/action/lang/en.php @@ -193,6 +193,11 @@ $lang->action->desc->syncexecution = '$date, starting the task sets the e $lang->action->desc->importfromgitlab = '$date, Issue associate created from gitlab by $actor.' . "\n"; $lang->action->desc->archived = '$date, archived by $actor.' . "\n"; $lang->action->desc->restore = '$date, restore by $actor.' . "\n"; +$lang->action->desc->importedcard = '$date, imported to $extra by $actor.' . "\n"; +$lang->action->desc->importedproductplan = '$date, imported to $extra by $actor.' . "\n"; +$lang->action->desc->importedrelease = '$date, imported to $extra by $actor.' . "\n"; +$lang->action->desc->importedexecution = '$date, imported to $extra by $actor.' . "\n"; +$lang->action->desc->importedbuild = '$date, imported to $extra by $actor.' . "\n"; /* Used to describe the history of operations related to parent-child tasks. */ $lang->action->desc->createchildren = '$date, $actor created a child task $extra。' . "\n"; @@ -332,6 +337,11 @@ $lang->action->label->closedbychild = 'closed'; $lang->action->label->activatedbychild = 'activated'; $lang->action->label->createchild = 'activated'; $lang->action->label->executed = 'executed'; +$lang->action->label->importedcard = 'imported'; +$lang->action->label->importedproductplan = 'imported'; +$lang->action->label->importedrelease = 'imported'; +$lang->action->label->importedexecution = 'imported'; +$lang->action->label->importedbuild = 'imported'; /* Dynamic information is grouped by object. */ $lang->action->dynamicAction = new stdclass; diff --git a/module/action/lang/zh-cn.php b/module/action/lang/zh-cn.php index 4442e8c448..8958d859e8 100755 --- a/module/action/lang/zh-cn.php +++ b/module/action/lang/zh-cn.php @@ -193,6 +193,11 @@ $lang->action->desc->syncexecution = '$date, 系统判断由于任务开 $lang->action->desc->importfromgitlab = '$date, 由 $actor 从Gitlab的Issue关联创建。' . "\n"; $lang->action->desc->archived = '$date, 由 $actor 归档。' . "\n"; $lang->action->desc->restore = '$date, 由 $actor 还原。' . "\n"; +$lang->action->desc->importedcard = '$date, 由 $actor 从看板 $extra 导入。' . "\n"; +$lang->action->desc->importedproductplan = '$date, 由 $actor 从产品计划 $extra 导入。' . "\n"; +$lang->action->desc->importedrelease = '$date, 由 $actor 从产品发布 $extra 导入。' . "\n"; +$lang->action->desc->importedexecution = '$date, 由 $actor 从项目执行 $extra 导入。' . "\n"; +$lang->action->desc->importedbuild = '$date, 由 $actor 从项目版本 $extra 导入。' . "\n"; /* 用来描述和父子任务相关的操作历史记录。*/ $lang->action->desc->createchildren = '$date, 由 $actor 创建子任务 $extra。' . "\n"; @@ -332,6 +337,11 @@ $lang->action->label->closedbychild = '关闭了'; $lang->action->label->activatedbychild = '激活了'; $lang->action->label->createchild = '激活了'; $lang->action->label->executed = '执行了'; +$lang->action->label->importedcard = '导入了'; +$lang->action->label->importedproductplan = '导入了'; +$lang->action->label->importedrelease = '导入了'; +$lang->action->label->importedexecution = '导入了'; +$lang->action->label->importedbuild = '导入了'; /* 动态信息按照对象分组 */ $lang->action->dynamicAction = new stdclass(); diff --git a/module/action/model.php b/module/action/model.php index 594e839a21..f379663975 100755 --- a/module/action/model.php +++ b/module/action/model.php @@ -435,6 +435,11 @@ class actionModel extends model $title = $this->dao->select('title')->from(TABLE_STORY)->where('id')->eq($action->extra)->fetch('title'); if($title) $action->extra = common::hasPriv('story', 'view') ? html::a(helper::createLink('story', 'view', "storyID=$action->extra"), "#$action->extra " . $title) : "#$action->extra " . $title; } + elseif($actionName == 'importedcard') + { + $title = $this->dao->select('name')->from(TABLE_KANBAN)->where('id')->eq($action->extra)->fetch('name'); + if($title) $action->extra = common::hasPriv('kanban', 'view') ? html::a(helper::createLink('kanban', 'view', "kanbanID=$action->extra"), "#$action->extra " . $title) : "#$action->extra " . $title; + } elseif($actionName == 'createchildren') { $names = $this->dao->select('id,name')->from(TABLE_TASK)->where('id')->in($action->extra)->fetchPairs('id', 'name'); @@ -1048,6 +1053,13 @@ class actionModel extends model $user = $this->dao->select('id,realname')->from(TABLE_USER)->where('id')->eq($action->objectID)->fetch(); if($user) $action->objectName = $user->realname; } + elseif($action->objectType == 'kanbancard' and strpos($action->action, 'imported') !== false and $action->action != 'importedcard') + { + $objectType = str_replace('imported', '', $action->action); + $objectTable = zget($this->config->objectTables, $objectType); + $objectName = $objectType == 'productplan' ? 'title' : 'name'; + $action->objectName = $this->dao->select($objectName)->from($objectTable)->where('id')->eq($action->extra)->fetch($objectName); + } $projectID = isset($relatedProjects[$action->objectType][$action->objectID]) ? $relatedProjects[$action->objectType][$action->objectID] : 0; diff --git a/module/build/model.php b/module/build/model.php index c6e9424310..525a6772dc 100644 --- a/module/build/model.php +++ b/module/build/model.php @@ -56,10 +56,11 @@ class buildModel extends model * @param string $type * @param int $param * @param string $orderBy + * @param object $pager * @access public * @return array */ - public function getProjectBuilds($projectID = 0, $type = 'all', $param = 0, $orderBy = 't1.date_desc,t1.id_desc') + public function getProjectBuilds($projectID = 0, $type = 'all', $param = 0, $orderBy = 't1.date_desc,t1.id_desc', $pager = null) { return $this->dao->select('t1.*, t2.name as executionName, t2.id as executionID, t3.name as productName, t4.name as branchName') ->from(TABLE_BUILD)->alias('t1') @@ -72,6 +73,7 @@ class buildModel extends model ->beginIF($type == 'product' and $param)->andWhere('t1.product')->eq($param)->fi() ->beginIF($type == 'bysearch')->andWhere($param)->fi() ->orderBy($orderBy) + ->page($pager) ->fetchAll('id'); } @@ -243,7 +245,7 @@ class buildModel extends model $branchName = $allBuilds[$buildID]->branchName ? $allBuilds[$buildID]->branchName : $this->lang->branch->main; $builds[$buildID] = (strpos($params, 'withbranch') !== false ? $branchName . '/' : '') . $releaseName; } - } + } return $sysBuilds + $builds + $selectedBuilds; } diff --git a/module/group/lang/resource.php b/module/group/lang/resource.php index 5c33bb7baa..47faf1c6e1 100644 --- a/module/group/lang/resource.php +++ b/module/group/lang/resource.php @@ -679,6 +679,7 @@ $lang->resource->kanban->restoreCard = 'restoreCard'; $lang->resource->kanban->setLaneHeight = 'setLaneHeight'; $lang->resource->kanban->batchCreateCard = 'batchCreateCard'; $lang->resource->kanban->import = 'import'; +$lang->resource->kanban->enableArchived = 'enableArchived'; $lang->kanban->methodOrder[5] = 'space'; $lang->kanban->methodOrder[10] = 'createSpace'; @@ -726,6 +727,7 @@ $lang->kanban->methodorder[210] = 'restoreCard'; $lang->kanban->methodorder[215] = 'setLaneHeight'; $lang->kanban->methodOrder[220] = 'batchCreateCard'; $lang->kanban->methodorder[225] = 'import'; +$lang->kanban->methodorder[230] = 'enableArchived'; /* Execution. */ $lang->resource->execution = new stdclass(); diff --git a/module/kanban/config.php b/module/kanban/config.php index 8c8339cdae..fe33b047f2 100644 --- a/module/kanban/config.php +++ b/module/kanban/config.php @@ -44,6 +44,12 @@ $config->kanban->editor->close = array('id' => 'comment', 'tools' => 'simp $config->kanban->editor->editcard = array('id' => 'desc', 'tools' => 'simpleTools'); $config->kanban->editor->viewcard = array('id' => 'comment', 'tools' => 'simpleTools'); +$config->kanban->fromType = array('execution', 'productplan', 'release', 'build'); +$config->kanban->executionField = array('name', 'status', 'end', 'PM', 'type', 'deleted'); +$config->kanban->productplanField = array(); +$config->kanban->releaseField = array(); +$config->kanban->buildField = array(); + $config->kanban->default = new stdclass(); $config->kanban->default->story = new stdclass(); $config->kanban->default->story->name = $lang->SRCommon; diff --git a/module/kanban/control.php b/module/kanban/control.php index 5f1a031eda..bb52678ed1 100644 --- a/module/kanban/control.php +++ b/module/kanban/control.php @@ -884,6 +884,266 @@ class kanban extends control die(json_encode($kanbanGroup)); } + /** + * Import card. + * + * @param int $kanbanID + * @param int $regionID + * @param int $groupID + * @param int $columnID + * @param int $selectedKanbanID + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function importCard($kanbanID = 0, $regionID = 0, $groupID = 0, $columnID = 0, $selectedKanbanID = 0, $recTotal = 0, $recPerPage = 30, $pageID = 1) + { + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + + $cards2Imported = $this->kanban->getCards2Import($selectedKanbanID, $kanbanID, $pager); + + if($_POST) + { + $importedIDList = $this->kanban->importCard($kanbanID, $regionID, $groupID, $columnID); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + foreach($importedIDList as $cardID) + { + $this->loadModel('action')->create('kanbancard', $cardID, 'importedcard', '', $cards2Imported[$cardID]->kanban); + } + + return print(js::locate($this->createLink('kanban', 'view', "kanbanID=$kanbanID"), 'parent.parent')); + } + + /* Find Kanban other than this kanban. */ + $kanbanPairs = $this->kanban->getKanbanPairs(); + unset($kanbanPairs[$kanbanID]); + + $this->view->cards2Imported = $cards2Imported; + $this->view->kanbanPairs = array($this->lang->kanban->allKanban) + $kanbanPairs; + $this->view->lanePairs = $this->kanban->getLanePairsByGroup($groupID); + $this->view->users = $this->loadModel('user')->getPairs('noletter|nodeleted'); + $this->view->pager = $pager; + $this->view->selectedKanbanID = $selectedKanbanID; + $this->view->kanbanID = $kanbanID; + $this->view->regionID = $regionID; + $this->view->groupID = $groupID; + $this->view->columnID = $columnID; + + $this->display(); + } + + /** + * Import plan. + * + * @param int $kanbanID + * @param int $regionID + * @param int $groupID + * @param int $columnID + * @param int $selectedProductID + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function importPlan($kanbanID = 0, $regionID = 0, $groupID = 0, $columnID = 0, $selectedProductID = 0, $recTotal = 0, $recPerPage = 30, $pageID = 1) + { + if($_POST) + { + $importedIDList = $this->kanban->importObject($kanbanID, $regionID, $groupID, $columnID, 'productplan'); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + foreach($importedIDList as $cardID => $planID) + { + $this->loadModel('action')->create('kanbancard', $cardID, 'importedProductplan', '', $planID); + } + + return print(js::locate($this->createLink('kanban', 'view', "kanbanID=$kanbanID"), 'parent.parent')); + } + + $this->loadModel('product'); + $this->loadModel('productplan'); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + + $productPairs = $this->product->getPairs(); + $selectedProductID = empty($selectedProductID) ? key($productPairs) : $selectedProductID; + + $this->view->products = $productPairs; + $this->view->selectedProductID = $selectedProductID; + $this->view->lanePairs = $this->kanban->getLanePairsByGroup($groupID); + $this->view->plans2Imported = $this->productplan->getList($selectedProductID, 0, 'all', $pager); + $this->view->pager = $pager; + $this->view->kanbanID = $kanbanID; + $this->view->regionID = $regionID; + $this->view->groupID = $groupID; + $this->view->columnID = $columnID; + + $this->display(); + } + + /** + * Import release. + * + * @param int $kanbanID + * @param int $regionID + * @param int $groupID + * @param int $columnID + * @param int $selectedProductID + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function importRelease($kanbanID = 0, $regionID = 0, $groupID = 0, $columnID = 0, $selectedProductID = 0, $recTotal = 0, $recPerPage = 30, $pageID = 1) + { + if($_POST) + { + $importedIDList = $this->kanban->importObject($kanbanID, $regionID, $groupID, $columnID, 'release'); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + foreach($importedIDList as $cardID => $releaseID) + { + $this->loadModel('action')->create('kanbancard', $cardID, 'importedRelease', '', $releaseID); + } + + return print(js::locate($this->createLink('kanban', 'view', "kanbanID=$kanbanID"), 'parent.parent')); + } + + $this->loadModel('product'); + $this->loadModel('release'); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + + $productPairs = $this->product->getPairs(); + $selectedProductID = empty($selectedProductID) ? key($productPairs) : $selectedProductID; + + $this->view->products = $productPairs; + $this->view->selectedProductID = $selectedProductID; + $this->view->lanePairs = $this->kanban->getLanePairsByGroup($groupID); + $this->view->releases2Imported = $this->release->getList($selectedProductID, 'all', 'all', 't1.date_desc', $pager); + $this->view->pager = $pager; + $this->view->kanbanID = $kanbanID; + $this->view->regionID = $regionID; + $this->view->groupID = $groupID; + $this->view->columnID = $columnID; + + $this->display(); + } + + /** + * Import build. + * + * @param int $kanbanID + * @param int $regionID + * @param int $groupID + * @param int $columnID + * @param int $selectedProjectID + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function importBuild($kanbanID = 0, $regionID = 0, $groupID = 0, $columnID = 0, $selectedProjectID = 0, $recTotal = 0, $recPerPage = 30, $pageID = 1) + { + if($_POST) + { + $importedIDList = $this->kanban->importObject($kanbanID, $regionID, $groupID, $columnID, 'build'); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + foreach($importedIDList as $cardID => $buildID) + { + $this->loadModel('action')->create('kanbancard', $cardID, 'importedBuild', '', $buildID); + } + + return print(js::locate($this->createLink('kanban', 'view', "kanbanID=$kanbanID"), 'parent.parent')); + } + + $this->loadModel('project'); + $this->loadModel('build'); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + + $projectPairs = $this->project->getPairsByProgram(); + $selectedProjectID = empty($selectedProjectID) ? key($projectPairs) : $selectedProjectID; + + $this->view->projects = $projectPairs; + $this->view->selectedProjectID = $selectedProjectID; + $this->view->lanePairs = $this->kanban->getLanePairsByGroup($groupID); + $this->view->builds2Imported = $this->build->getProjectBuilds($selectedProjectID, 'all', 0, 't1.date_desc,t1.id_desc', $pager);; + $this->view->users = $this->loadModel('user')->getPairs('noletter|nodeleted'); + $this->view->pager = $pager; + $this->view->kanbanID = $kanbanID; + $this->view->regionID = $regionID; + $this->view->groupID = $groupID; + $this->view->columnID = $columnID; + + $this->display(); + } + + /** + * Import execution. + * + * @param int $kanbanID + * @param int $regionID + * @param int $groupID + * @param int $columnID + * @param int $selectedProjectID + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function importExecution($kanbanID = 0, $regionID = 0, $groupID = 0, $columnID = 0, $selectedProjectID = 0, $recTotal = 0, $recPerPage = 30, $pageID = 1) + { + if($_POST) + { + $importedIDList = $this->kanban->importObject($kanbanID, $regionID, $groupID, $columnID, 'execution'); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + foreach($importedIDList as $cardID => $executionID) + { + $this->loadModel('action')->create('kanbancard', $cardID, 'importedExecution', '', $executionID); + } + + return print(js::locate($this->createLink('kanban', 'view', "kanbanID=$kanbanID"), 'parent.parent')); + } + + $this->loadModel('project'); + $this->loadModel('execution'); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + + $this->view->projects = $this->project->getPairsByProgram(); + $this->view->selectedProjectID = $selectedProjectID; + $this->view->lanePairs = $this->kanban->getLanePairsByGroup($groupID); + $this->view->executions2Imported = $this->project->getStats($selectedProjectID, 'undone', 0, 0, 30, 'id_asc', $pager); + $this->view->users = $this->loadModel('user')->getPairs('noletter|nodeleted'); + $this->view->pager = $pager; + $this->view->kanbanID = $kanbanID; + $this->view->regionID = $regionID; + $this->view->groupID = $groupID; + $this->view->columnID = $columnID; + + $this->display(); + } + /** * Set a card's color. * @@ -1381,8 +1641,8 @@ class kanban extends control $kanban = $this->kanban->getByID($kanbanID); - $this->view->enableImport = empty($kanban->importObject) ? 'off' : 'on'; - $this->view->importObjects = empty($kanban->importObject) ? array() : explode(',', $kanban->importObject); + $this->view->enableImport = empty($kanban->object) ? 'off' : 'on'; + $this->view->importObjects = empty($kanban->object) ? array() : explode(',', $kanban->object); $this->display(); } diff --git a/module/kanban/css/closespace.css b/module/kanban/css/closespace.css new file mode 100644 index 0000000000..9235410476 --- /dev/null +++ b/module/kanban/css/closespace.css @@ -0,0 +1 @@ +#mainContent .c-comment {width: 50px} diff --git a/module/kanban/css/common.css b/module/kanban/css/common.css index b957733002..0196d3fe8e 100644 --- a/module/kanban/css/common.css +++ b/module/kanban/css/common.css @@ -1,3 +1,4 @@ .color-picker-item {width: 20px; height: 20px; margin-right: 10px; border-radius: 50% 50%; float: left; cursor: pointer; text-align: center; line-height: 20px;} .color-picker-item > .icon {color: #fff; display: none} .checked > .icon {display: inline-block;} +.p-10px {padding: 10px} diff --git a/module/kanban/css/space.css b/module/kanban/css/space.css index 43f6d7d70e..ef41c8d6b4 100644 --- a/module/kanban/css/space.css +++ b/module/kanban/css/space.css @@ -7,6 +7,7 @@ #spaceList .spaceActions > a{margin-left: 10px;} .space .menu {margin-left: 5px; margin-right:5px;} .space .table-empty-tip {padding: 40px 10px;} +.space .spaceTitle.pull-left {overflow: hidden; white-space: nowrap; max-width: calc(100% - 255px)} .kanbans > .col {width: 25% !important;} @media screen and (max-width: 1500px) {.kanbans > .col {width: 25%;}} diff --git a/module/kanban/css/view.css b/module/kanban/css/view.css index 1a4c04e8e6..db3016aa6c 100644 --- a/module/kanban/css/view.css +++ b/module/kanban/css/view.css @@ -98,5 +98,19 @@ .kanban-col[data-type=ADD] {display: none;} .kanban-col[data-type=EMPTY] {display: none;} -.kanban-item:hover .title {padding-right: 10px;} +.kanban-item .title {padding-right: 10px;} .gray .actions {display:none;} + +.kanban-card .header .executionName {display: flex; width: 100%; float: left; white-space: nowrap; overflow: hidden;} +.kanban-card .header .executionName .title {padding-right: 0px; margin-right: 5px; white-space: nowrap; overflow: hidden;} +.kanban-card .header .executionName .title .icon {padding-right: 5px;} +.kanban-card .header .executionName > span {flex: none;} +.execInfo {margin-top: 23px;} +.execInfo .execStatus{margin-right: 20px;} +.execInfo .label-wait {background: #EFEFEF !important; color: #838A9D;} +.execInfo .label-doing {background: #f8d2d2 !important; color: #e64b4c;} +.execInfo .label-suspended {background: #fff3d9 !important; color: #ff9800;} +.execInfo .label-closed {background: #e5e5e5 !important; color: #b8b8b8;} +.execInfo .label-deleted {background: #ff5d5d !important; color: #fff;} +.region .kanban-item > .kanban-card > .execInfo > .user {position: absolute; right: 10px; bottom: -2px} +.region .kanban-item > .kanban-card > .execInfo > .user > .avatar {display: inline-block; width: 24px; height: 24px; line-height: 24px; margin-right: 0px; margin-left: 2px} diff --git a/module/kanban/js/common.js b/module/kanban/js/common.js index fc66b36f5b..c9177e6d58 100644 --- a/module/kanban/js/common.js +++ b/module/kanban/js/common.js @@ -45,3 +45,26 @@ function initColorPicker() }) } +/** + * Reload object list. + * + * @param int $targetID + * @access public + * @return void + */ +function reloadObjectList(targetID) +{ + location.href = createLink('kanban', methodName, 'kanbanID=' + kanbanID + '®ionID=' + regionID + '&groupID=' + groupID + '&columnID=' + columnID + '&targetID=' + targetID); +} + +/** + * Set target lane ID. + * + * @param int $targetLaneID + * @access public + * @return void + */ +function setTargetLane(targetLaneID) +{ + $('#targetLane').val(targetLaneID); +} diff --git a/module/kanban/js/view.js b/module/kanban/js/view.js index 5aa4017a8b..a1e3933463 100644 --- a/module/kanban/js/view.js +++ b/module/kanban/js/view.js @@ -197,6 +197,17 @@ function renderCount($count, count, column) } } +/** + * Render lane name. + * + * @param object $lane + * @param object lane + * @param object $kanban + * @param object columns + * @param object kanban + * @access public + * @return void + */ function renderLaneName($lane, lane, $kanban, columns, kanban) { var canSet = lane.actions.includes('setLane'); @@ -265,99 +276,190 @@ function renderUsersAvatar(users, itemID, size) */ function renderKanbanItem(item, $item) { - var $title = $item.children('.title'); - var privs = item.actions; - var printMoreBtn = (privs.includes('editCard') || privs.includes('finishCard') || privs.includes('activateCard') ||privs.includes('archiveCard') || privs.includes('copyCard') || privs.includes('deleteCard') || privs.includes('moveCard') || privs.includes('setCardColor')); - - if(kanban.performable == 1 && item.status == 'done' ) + if(item.fromType == 'execution') { - $item.closest('.kanban-card').css({'filter' : 'opacity(0.5)', 'text-decoration' : 'line-through'}); + renderExecutionItem(item, $item); } else { - $item.closest('.kanban-card').css({'filter' : '', 'text-decoration' : ''}); - } + var $title = $item.children('.title'); + var privs = item.actions; + var printMoreBtn = (privs.includes('editCard') || privs.includes('archiveCard') || privs.includes('copyCard') || privs.includes('deleteCard') || privs.includes('moveCard') || privs.includes('setCardColor')); - if(privs.includes('sortCard')) $item.parent().addClass('sort'); - if(!$title.length) - { - if(privs.includes('viewCard')) $title = $('').appendTo($item).attr('href', createLink('kanban', 'viewCard', 'cardID=' + item.id, '', true)); - if(!privs.includes('viewCard')) $title = $('

').appendTo($item); - } + if(kanban.performable == 1 && item.status == 'done' ) + { + $item.closest('.kanban-card').css({'filter' : 'opacity(0.5)', 'text-decoration' : 'line-through'}); + } + else + { + $item.closest('.kanban-card').css({'filter' : '', 'text-decoration' : ''}); + } - $title.text(item.name).attr('title', item.name); + if(privs.includes('sortCard')) $item.parent().addClass('sort'); + if(!$title.length) + { + if(privs.includes('viewCard')) $title = $('').appendTo($item).attr('href', createLink('kanban', 'viewCard', 'cardID=' + item.id, '', true)); + if(!privs.includes('viewCard')) $title = $('

').appendTo($item); + } - if(printMoreBtn) - { - $( + $title.text(item.name).attr('title', item.name); + + if(printMoreBtn) + { + $( + [ + '
', + '', + '', + '', + '
' + ].join('')).appendTo($item); + } + + var $info = $item.children('.info'); + if(!$info.length) $info = $( [ - '
', - '', - '', - '', + '
', + '', + '', + '', + '
', '
' ].join('')).appendTo($item); - } - var $info = $item.children('.info'); - if(!$info.length) $info = $( + $item.data('card', item); + + $info.children('.estimate').text(item.estimate + kanbancardLang.lblHour); + if(item.estimate == 0) $info.children('.estimate').hide(); + + $info.children('.pri') + .attr('class', 'pri label-pri label-pri-' + item.pri) + .text(item.pri); + + $item.css('background-color', item.color); + $item.toggleClass('has-color', item.color != '#fff' && item.color != ''); + $item.find('.info > .label-light').css('background-color', item.color); + + var $time = $info.children('.time'); + if(item.end == '0000-00-00' && item.begin == '0000-00-00') + { + $time.hide(); + } + else + { + var today = new Date(); + var begin = $.zui.createDate(item.begin); + var end = $.zui.createDate(item.end); + var needRemind = (begin.toLocaleDateString() == today.toLocaleDateString() || end.toLocaleDateString() == today.toLocaleDateString()); + if(item.end == '0000-00-00' && item.begin != '0000-00-00') + { + $time.text($.zui.formatDate(begin, 'MM/dd') + ' ' + kanbancardLang.beginAB).attr('title', $.zui.formatDate(begin, 'yyyy/MM/dd') + ' ' +kanbancardLang.beginAB).show(); + } + else if(item.begin == '0000-00-00' && item.end != '0000-00-00') + { + $time.text($.zui.formatDate(end, 'MM/dd') + ' ' + kanbancardLang.deadlineAB).attr('title', $.zui.formatDate(end, 'yyyy/MM/dd') + ' ' + kanbancardLang.deadlineAB).show(); + } + else if(item.begin != '0000-00-00' && item.end != '0000-00-00') + { + $time.text($.zui.formatDate(begin, 'MM/dd') + ' ' + kanbancardLang.to + ' ' + $.zui.formatDate(end, 'MM/dd')).attr('title', $.zui.formatDate(begin, 'yyyy/MM/dd') + kanbancardLang.to + $.zui.formatDate(end, 'yyyy/MM/dd')).show(); + } + + if(!$item.hasClass('has-color') && needRemind) $time.css('background-color', 'rgba(210, 50, 61, 0.3)'); + if($item.hasClass('has-color') && needRemind) $time.css('background-color', 'rgba(255, 255, 255, 0.3)'); + if(!needRemind) $time.css('background-color', 'rgba(0, 0, 0, 0.15)'); + } + + /* Display avatars of assignedTo. */ + var assignedTo = item.assignedTo.split(','); + var $user = $info.children('.user'); + var title = []; + for(i = 0; i < assignedTo.length; i++) title.push(users[assignedTo[i]]); + $user.html(renderUsersAvatar(assignedTo, item.id)).attr('title', title); + } +} + +/** + * Render execution item. + * + * @param object item + * @param object $item + * @access public + * @return void + */ +function renderExecutionItem(item, $item) +{ + /* Output header information. */ + var privs = item.actions; + var $header = $item.children('.header'); + if(!$header.length) $header = $( [ - '
', - '', - '', - '', - '
', + '
', '
' ].join('')).appendTo($item); + var $titleBox = $header.children('.executionName'); + if(!$titleBox.length) $titleBox = $( + [ + '
', + '
' + ].join('')).appendTo($header); + + /* Print execution name. */ + var $title = $titleBox.children('.title'); + if(!$title.length) + { + var viewMethod = item.execType == 'kanban' ? 'kanban' : 'view'; + if(privs.includes('viewExecution') && item.deleted == '0') $title = $('' + item.name + '').appendTo($titleBox).attr('href', createLink('execution', viewMethod, 'executionID=' + item.fromID)); + if(!privs.includes('viewExecution') || item.deleted == '1') $title = $('' + item.name + '').appendTo($titleBox); + } + $title.attr('title', item.name); + + if(item.delay) + { + $delayed = $titleBox.children('.delayed'); + if(!$delayed.length) + { + $('' + executionLang.delayed + '').appendTo($titleBox); + } + } + $item.data('card', item); - $info.children('.estimate').text(item.estimate + kanbancardLang.lblHour); - if(item.estimate == 0) $info.children('.estimate').hide(); + var $info = $item.children('.execInfo'); + if(!$info.length) $info = $( + [ + '
', + '
' + ].join('')).appendTo($item); - $info.children('.pri') - .attr('class', 'pri label-pri label-pri-' + item.pri) - .text(item.pri); - - $item.css('background-color', item.color); - $item.toggleClass('has-color', item.color != '#fff' && item.color != ''); - $item.find('.info > .label-light').css('background-color', item.color); - - var $time = $info.children('.time'); - if(item.end == '0000-00-00' && item.begin == '0000-00-00') + var $statusBox = $info.children('.execStatus'); + if(!$statusBox.length) { - $time.hide(); - } - else - { - var today = new Date(); - var begin = $.zui.createDate(item.begin); - var end = $.zui.createDate(item.end); - var needRemind = (begin.toLocaleDateString() == today.toLocaleDateString() || end.toLocaleDateString() == today.toLocaleDateString()); - if(item.end == '0000-00-00' && item.begin != '0000-00-00') + if(item.deleted == '0') { - $time.text($.zui.formatDate(begin, 'MM/dd') + ' ' + kanbancardLang.beginAB).attr('title', $.zui.formatDate(begin, 'yyyy/MM/dd') + ' ' +kanbancardLang.beginAB).show(); + $statusBox = $('' + executionLang.statusList[item.status] + '').appendTo($info); } - else if(item.begin == '0000-00-00' && item.end != '0000-00-00') + else { - $time.text($.zui.formatDate(end, 'MM/dd') + ' ' + kanbancardLang.deadlineAB).attr('title', $.zui.formatDate(end, 'yyyy/MM/dd') + ' ' + kanbancardLang.deadlineAB).show(); + $statusBox = $('' + executionLang.deleted + '').appendTo($info); } - else if(item.begin != '0000-00-00' && item.end != '0000-00-00') - { - $time.text($.zui.formatDate(begin, 'MM/dd') + ' ' + kanbancardLang.to + ' ' + $.zui.formatDate(end, 'MM/dd')).attr('title', $.zui.formatDate(begin, 'yyyy/MM/dd') + kanbancardLang.to + $.zui.formatDate(end, 'yyyy/MM/dd')).show(); - } - - if(!$item.hasClass('has-color') && needRemind) $time.css('background-color', 'rgba(210, 50, 61, 0.3)'); - if($item.hasClass('has-color') && needRemind) $time.css('background-color', 'rgba(255, 255, 255, 0.3)'); - if(!needRemind) $time.css('background-color', 'rgba(0, 0, 0, 0.15)'); } - /* Display avatars of assignedTo. */ - var assignedTo = item.assignedTo.split(','); + /* Display deadline of execution. */ + var $date = $info.children('.date'); + var end = $.zui.createDate(item.end); + var today = new Date(); + var labelType = end.toLocaleDateString() == today.toLocaleDateString() ? 'danger' : 'wait'; + if(!$date.length) $date = $('').appendTo($info); + + $date.text($.zui.formatDate(end, 'MM-dd') + ' ' + kanbancardLang.deadlineAB).attr('title', $.zui.formatDate(end, 'yyyy-MM-dd') + ' ' + kanbancardLang.deadlineAB).show(); + + /* Display avatars of PM. */ var $user = $info.children('.user'); - var title = []; - for(i = 0; i < assignedTo.length; i++) title.push(users[assignedTo[i]]); - $user.html(renderUsersAvatar(assignedTo, item.id)).attr('title', title); + var user = [item.PM]; + if(!$user.length) $user = $('
').appendTo($info); + + $user.html(renderUsersAvatar(user, item.id)).attr('title', item.PM); } /** @@ -865,8 +967,12 @@ function createColumnCreateMenu(options) var laneID = col.$kanbanData.lanes[0].id ? col.$kanbanData.lanes[0].id : 0; var columnID = col.id; - if(privs.includes('createCard')) items.push({label: kanbanLang.createCard, url: $.createLink('kanban', 'createCard', 'kanbanID=' + kanbanID + '®ionID=' + regionID + '&groupID=' + groupID + '&columnID=' + columnID), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}}); + if(privs.includes('createCard')) items.push({label: kanbanLang.createCard, url: $.createLink('kanban', 'createCard', 'kanbanID=' + kanbanID + '®ionID=' + regionID + '&groupID=' + groupID + '&columnID=' + columnID), className: 'iframe', attrs: {'data-toggle': 'modal'}}); if(privs.includes('batchCreateCard')) items.push({label: kanbanLang.batchCreateCard, url: $.createLink('kanban', 'batchCreateCard', 'kanbanID=' + kanbanID + '®ionID=' + regionID + '&groupID=' + groupID + '&laneID=' + laneID + '&columnID=' + columnID), attrs: {'data-width': '80%'}}); + if(privs.includes('import') && kanban.object.indexOf('cards') != -1) items.push({label: kanbanLang.importCard, url: $.createLink('kanban', 'importCard', 'kanbanID=' + kanbanID + '®ionID=' + regionID + '&groupID=' + groupID + '&laneID=' + laneID + '&columnID=' + columnID), attrs: {'data-width': '80%'}}); + if(privs.includes('import') && kanban.object) items.push({className: 'parentDivider'}); + if(privs.includes('import') && kanban.object) items.push({label: kanbanLang.importAB, className: 'import'}); + return items; } @@ -984,6 +1090,18 @@ $(function() if(!items || !items.length) return; $.zui.ContextMenu.show(items, items.$options || {event: event}); + + $('.parentDivider').parent().addClass('divider'); + $('.import').parent().addClass('dropdown-submenu top'); + + /* The submenu of import. */ + var importPlanLink = kanban.object.indexOf('plan') != -1 ? '
  • ' + kanbanLang.importPlan + '
  • ' : ''; + var importReleaseLink = kanban.object.indexOf('release') != -1 ? '
  • ' + kanbanLang.importRelease + '
  • ' : ''; + var importExecutionLink = kanban.object.indexOf('execution') != -1 ? '
  • ' + kanbanLang.importExecution + '
  • ' : ''; + var importBuildLink = kanban.object.indexOf('build') != -1 ? '
  • ' + kanbanLang.importBuild + '
  • ' : ''; + var importSubmenu = ''; + $('.import').parent().append(importSubmenu); + }); /* Adjust the add button position on window resize */ @@ -1097,6 +1215,11 @@ $(function() hideKanbanAction(); } + if(sortType == 'column') + { + $('.kanban-item').addClass('hidden'); + } + if(sortType == 'card') { oldLaneID = e.element.closest('.kanban-lane').data('id'); @@ -1160,6 +1283,7 @@ $(function() { updateRegion(regionID, response[regionID]); } + $('.kanban-item').removeClass('hidden'); }); }, always: function(e) diff --git a/module/kanban/lang/en.php b/module/kanban/lang/en.php index 05fd40ec65..5245305bcb 100644 --- a/module/kanban/lang/en.php +++ b/module/kanban/lang/en.php @@ -63,7 +63,16 @@ $lang->kanban->restore = 'Restore'; $lang->kanban->child = 'Child'; $lang->kanban->batchCreateCard = 'Batchcreate Card'; $lang->kanban->import = 'Import'; +$lang->kanban->importAB = 'Import'; $lang->kanban->showClosed = 'Closed'; +$lang->kanban->importCard = 'Import Card'; +$lang->kanban->importPlan = 'Import Plan'; +$lang->kanban->importRelease = 'Import Release'; +$lang->kanban->importExecution = 'Import ' . $lang->execution->common; +$lang->kanban->importBuild = 'Import Build'; +$lang->kanban->allKanban = 'All Kanban'; +$lang->kanban->allProjects = 'All Projects'; +$lang->kanban->allProducts = 'All Products'; /* Fields. */ $lang->kanban->space = 'Space'; @@ -93,6 +102,11 @@ $lang->kanban->accessDenied = "You have no access to the kanban."; $lang->kanban->confirmDelete = 'Do you want to delete this?'; $lang->kanban->cardCountTip = 'Please enter the number of cards'; +$lang->kanban->selectedKanban = 'Please select Kanban'; +$lang->kanban->selectedProduct = 'Please select Product'; +$lang->kanban->selectedProject = 'Please select Project'; +$lang->kanban->selectedLane = 'Target Lane'; + $lang->kanban->aclGroup['open'] = 'Open'; $lang->kanban->aclGroup['private'] = 'Private'; $lang->kanban->aclGroup['extend'] = 'Extend'; diff --git a/module/kanban/lang/zh-cn.php b/module/kanban/lang/zh-cn.php index 5a013d06a8..211be14f43 100644 --- a/module/kanban/lang/zh-cn.php +++ b/module/kanban/lang/zh-cn.php @@ -63,7 +63,16 @@ $lang->kanban->restore = '还原'; $lang->kanban->child = '子'; $lang->kanban->batchCreateCard = '批量创建卡片'; $lang->kanban->import = '导入功能'; +$lang->kanban->importAB = '导入'; $lang->kanban->showClosed = '显示已关闭'; +$lang->kanban->importCard = '转入卡片'; +$lang->kanban->importPlan = '导入产品计划'; +$lang->kanban->importRelease = '导入产品发布'; +$lang->kanban->importExecution = '导入项目' . $lang->execution->common; +$lang->kanban->importBuild = '导入项目版本'; +$lang->kanban->allKanban = '所有看板'; +$lang->kanban->allProjects = '所有项目'; +$lang->kanban->allProducts = '所有产品'; /* Fields. */ $lang->kanban->space = '所属空间'; @@ -93,6 +102,11 @@ $lang->kanban->accessDenied = '您无权访问该看板'; $lang->kanban->confirmDelete = '您确认删除吗?'; $lang->kanban->cardCountTip = '请输入卡片数量'; +$lang->kanban->selectedKanban = '请选择看板'; +$lang->kanban->selectedProduct = '请选择产品'; +$lang->kanban->selectedProject = '请选择项目'; +$lang->kanban->selectedLane = '目标泳道'; + $lang->kanban->aclGroup['open'] = '公开'; $lang->kanban->aclGroup['private'] = '私有'; $lang->kanban->aclGroup['extend'] = '继承空间'; diff --git a/module/kanban/model.php b/module/kanban/model.php index aa0a7c581f..0e5b024752 100644 --- a/module/kanban/model.php +++ b/module/kanban/model.php @@ -469,12 +469,87 @@ class kanbanModel extends model } /** - * Batch create kanban cards. + * Import card. + * + * @param int $kanbanID + * @param int $regionID + * @param int $groupID + * @param int $columnID + * @access public + * @return bool|array + */ + public function importCard($kanbanID, $regionID, $groupID, $columnID) + { + $data = fixer::input('post')->get(); + $importIDList = $data->cards; + $targetLaneID = $data->targetLane; + + $oldCardsKanban = $this->dao->select('id,kanban')->from(TABLE_KANBANCARD)->where('id')->in($importIDList)->fetchPairs(); + + $updateData = new stdClass(); + $updateData->kanban = $kanbanID; + $updateData->region = $regionID; + $updateData->group = $groupID; + $this->dao->update(TABLE_KANBANCARD)->data($updateData)->where('id')->in($importIDList)->exec(); + + if(!dao::isError()) + { + $this->removeKanbanCell('common', $importIDList, $oldCardsKanban); + + $cards = implode(',', $importIDList); + $this->addKanbanCell($kanbanID, $targetLaneID, $columnID, 'common', $cards); + + return $importIDList; + } + + return false; + } + + /** + * Import object. * * @param int $kanbanID * @param int $regionID * @param int $groupID * @param int $columnID + * @param string $objectType + * @access public + * @return array + */ + public function importObject($kanbanID, $regionID, $groupID, $columnID, $objectType) + { + $data = fixer::input('post')->get(); + $objectIDList = $data->{$objectType . 's'}; + $targetLaneID = $data->targetLane; + + $objectCards = array(); + foreach($objectIDList as $objectID) + { + $cardData = new stdClass(); + $cardData->kanban = $kanbanID; + $cardData->region = $regionID; + $cardData->group = $groupID; + $cardData->fromID = $objectID; + $cardData->fromType = $objectType; + $this->dao->insert(TABLE_KANBANCARD)->data($cardData)->exec(); + + $cardID = $this->dao->lastInsertID(); + $objectCards[$cardID] = $objectID; + } + + if(!dao::isError()) + { + $cards = implode(',', array_keys($objectCards)); + $this->addKanbanCell($kanbanID, $targetLaneID, $columnID, 'common', $cards); + + return $objectCards; + } + + return false; + } + + /* + * Batch create kanban cards. * @access public * @return void */ @@ -567,7 +642,7 @@ class kanbanModel extends model * * @param int $kanbanID * @access public - * @return void + * @return array */ public function getKanbanData($kanbanID) { @@ -886,7 +961,7 @@ class kanbanModel extends model ->orderBy($order) ->fetchGroup('group'); - $actions = array('createColumn', 'setColumn', 'setWIP', 'archiveColumn', 'restoreColumn', 'deleteColumn', 'createCard', 'batchCreateCard', 'splitColumn', 'sortColumn'); + $actions = array('createColumn', 'setColumn', 'setWIP', 'archiveColumn', 'restoreColumn', 'deleteColumn', 'createCard', 'batchCreateCard', 'splitColumn', 'import', 'sortColumn'); /* Group by parent. */ $parentColumnGroup = array(); @@ -940,18 +1015,25 @@ class kanbanModel extends model public function getCardGroupByKanban($kanbanID) { /* Get card data.*/ - $cards = $this->dao->select('*')->from(TABLE_KANBANCARD)->alias('t1') + $cards = $this->dao->select('*')->from(TABLE_KANBANCARD) ->where('deleted')->eq(0) ->andWhere('kanban')->eq($kanbanID) ->andWhere('archived')->eq(0) + ->andWhere('fromID')->eq(0) ->fetchAll('id'); + foreach($this->config->kanban->fromType as $fromType) + { + $cards = $this->getImportedCards($kanbanID, $cards, $fromType); + } + $cellList = $this->dao->select('*')->from(TABLE_KANBANCELL) ->where('kanban')->eq($kanbanID) ->andWhere('type')->eq('common') ->fetchAll(); - $actions = array('editCard', 'finishCard', 'activateCard', 'archiveCard', 'deleteCard', 'moveCard', 'setCardColor', 'viewCard', 'sortCard'); + $actions = array('editCard', 'finishCard', 'activateCard', 'archiveCard', 'deleteCard', 'moveCard', 'setCardColor', 'viewCard', 'sortCard', 'viewExecution'); + $cardGroup = array(); foreach($cellList as $cell) { @@ -969,6 +1051,15 @@ class kanbanModel extends model $card->actions = array(); foreach($actions as $action) { + if($action == 'viewExecution') + { + if($card->fromType == 'execution') + { + if($card->execType == 'kanban' and common::hasPriv('execution', 'kanban')) $card->actions[] = $action; + if($card->execType != 'kanban' and common::hasPriv('execution', 'view')) $card->actions[] = $action; + } + continue; + } if(common::hasPriv('kanban', $action)) $card->actions[] = $action; } @@ -979,6 +1070,54 @@ class kanbanModel extends model return $cardGroup; } + /** + * Get imported cards. + * + * @param int $kanbanID + * @param object $cards + * @param string $fromType + * @access public + * @return array + */ + public function getImportedCards($kanbanID, $cards, $fromType) + { + $objectCards = $this->dao->select('*')->from(TABLE_KANBANCARD) + ->where('deleted')->eq(0) + ->andWhere('kanban')->eq($kanbanID) + ->andWhere('archived')->eq(0) + ->andWhere('fromType')->eq($fromType) + ->fetchAll('fromID'); + + if(!empty($objectCards)) + { + $table = $this->config->objectTables[$fromType]; + $objects = $this->dao->select('*')->from($table) + ->where('id')->in(array_keys($objectCards)) + ->fetchAll('id'); + + foreach($objectCards as $objectCard) + { + $object = $objects[$objectCard->fromID]; + $fieldType = $fromType . 'Field'; + + foreach($this->config->kanban->$fieldType as $field) $objectCard->$field = $object->$field; + + if($fromType =='execution') + { + if($object->status != 'done' and $object->status != 'closed' and $object->status != 'suspended') + { + $delay = helper::diffDate(helper::today(), $object->end); + if($delay > 0) $objectCard->delay = $delay; + } + $objectCard->execType = $object->type; + } + + $cards[$objectCard->id] = $objectCard; + } + } + return $cards; + } + /** * Get RD column group by regions. * @@ -1470,6 +1609,23 @@ class kanbanModel extends model ->fetchPairs('id'); } + /** + * Get Kanban pairs. + * + * @access public + * @return void + */ + public function getKanbanPairs() + { + $kanbanIdList = $this->getCanViewObjects('kanban'); + + return $this->dao->select('id,name')->from(TABLE_KANBAN) + ->where('deleted')->eq(0) + ->beginIF(!$this->app->user->admin)->andWhere('id')->in($kanbanIdList)->fi() + ->orderBy('id_desc') + ->fetchPairs('id'); + } + /** * Get can view objects. * @@ -2034,6 +2190,33 @@ class kanbanModel extends model } } + /** + * Remove kanban cell. + * + * @param string $type + * @param int|array $removeCardID + * @param array $kanbanList + * @access public + * @return void + */ + public function removeKanbanCell($type, $removeCardID, $kanbanList) + { + $removeIDList = is_array($removeCardID) ? $removeCardID : array($removeCardID); + foreach($removeIDList as $cardID) + { + if(empty($cardID)) continue; + + $this->dbh->query("UPDATE " . TABLE_KANBANCELL. " SET `cards` = REPLACE(cards, ',$cardID,', ',') WHERE `type` = '$type' AND `kanban` = {$kanbanList[$cardID]}"); + } + + $this->dao->update(TABLE_KANBANCELL) + ->set('cards')->eq('') + ->where('cards')->eq(',') + ->andWhere('type')->eq($type) + ->andWhere('kanban')->in($kanbanList) + ->exec(); + } + /** * Create a default RD kanban. * @@ -3100,7 +3283,7 @@ class kanbanModel extends model * @access public * @return array */ - public function getCardsByObject($objectType = '', $objectID = 0, $archived = 0, $deleted = '0') + public function getCardsByObject($objectType = '', $objectID = 0, $archived = '0', $deleted = '0') { return $this->dao->select('*')->from(TABLE_KANBANCARD) ->where(true) @@ -3111,6 +3294,28 @@ class kanbanModel extends model ->fetchAll('id'); } + /** + * Get cards to import. + * + * @param int $kanbanID + * @param int $excludedID + * @param obj $pager + * @access public + * @return array + */ + public function getCards2Import($kanbanID = 0, $excludedID = 0, $pager = null) + { + return $this->dao->select('*')->from(TABLE_KANBANCARD) + ->where('deleted')->eq(0) + ->andWhere('archived')->eq(0) + ->andWhere('fromID')->eq(0) + ->beginIF($kanbanID)->andWhere('kanban')->eq($kanbanID)->fi() + ->beginIF($excludedID)->andWhere('kanban')->ne($excludedID)->fi() + ->orderBy('order') + ->page($pager) + ->fetchAll('id'); + } + /** * Get Kanban cards menus by execution id. * @@ -3263,6 +3468,6 @@ class kanbanModel extends model $importObjects = $_POST['import'] == 'off' ? array() : $_POST['importObjectList']; $importObjectList = implode(',', $importObjects); - $this->dao->update(TABLE_KANBAN)->set('importObject')->eq($importObjectList)->where('id')->eq($kanbanID)->exec(); + $this->dao->update(TABLE_KANBAN)->set('object')->eq($importObjectList)->where('id')->eq($kanbanID)->exec(); } } diff --git a/module/kanban/view/closespace.html.php b/module/kanban/view/closespace.html.php index aaeba69cb5..f8910f48eb 100644 --- a/module/kanban/view/closespace.html.php +++ b/module/kanban/view/closespace.html.php @@ -23,11 +23,11 @@
    - - + + - diff --git a/module/kanban/view/create.html.php b/module/kanban/view/create.html.php index f1f8d4a00e..00c8574f48 100644 --- a/module/kanban/view/create.html.php +++ b/module/kanban/view/create.html.php @@ -43,7 +43,6 @@ diff --git a/module/kanban/view/edit.html.php b/module/kanban/view/edit.html.php index 585028f9af..f0e59e839c 100644 --- a/module/kanban/view/edit.html.php +++ b/module/kanban/view/edit.html.php @@ -36,7 +36,6 @@ diff --git a/module/kanban/view/importbuild.html.php b/module/kanban/view/importbuild.html.php new file mode 100644 index 0000000000..e148aa7f65 --- /dev/null +++ b/module/kanban/view/importbuild.html.php @@ -0,0 +1,83 @@ + + * @package kanban + * @version $Id: importbuild.html.php 5090 2022-01-19 14:19:24Z xieqiyu@cnezsoft.com $ + * @link https://www.zentao.net + */ +?> + + + + + +app->rawMethod);?> +
    +
    +
    +

    kanban->importBuild;?>

    +
    +
    +
    +

    kanban->selectedProject;?>

    +
    +
    +
    +

    kanban->selectedLane;?>

    +
    +
    + +
    comment;?>comment;?>
    + kanban->closeSpace);?>
    - fetch('my', 'buildContactLists');?>
    team, "class='form-control chosen' multiple data-drop_direction='down'");?> - fetch('my', 'buildContactLists');?>
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + idAB;?> +
    build->product;?>build->name;?>executionCommon;?>build->date;?>build->builder;?>
    +
    + + +
    + id);?> +
    productName;?>id", $build->name, '', "class='iframe'", true, true);?>name;?>executionName;?>date;?>builder);?>
    + + + +
    +
    + diff --git a/module/kanban/view/importcard.html.php b/module/kanban/view/importcard.html.php new file mode 100644 index 0000000000..7b6e79662c --- /dev/null +++ b/module/kanban/view/importcard.html.php @@ -0,0 +1,83 @@ + + * @package kanban + * @version $Id: importcard.html.php 5090 2022-01-19 14:19:24Z xieqiyu@cnezsoft.com $ + * @link https://www.zentao.net + */ +?> + + + + + +app->rawMethod);?> +
    +
    +
    +

    kanban->importCard;?>

    +
    +
    +
    +

    kanban->selectedKanban;?>

    +
    +
    +
    +

    kanban->selectedLane;?>

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + idAB;?> +
    kanban->name;?>priAB;?>kanbancard->name;?>kanbancard->assignedTo;?>kanbancard->end;?>
    +
    + + +
    + id);?> +
    kanban);?>pri?>' title='kanbancard->priList, $card->pri, $card->pri);?>'>kanbancard->priList, $card->pri, $card->pri);?>id", $card->name, '', "class='iframe'", true, true);?>name;?>assignedTo);?>end;?>
    + + + +
    +
    + diff --git a/module/kanban/view/importexecution.html.php b/module/kanban/view/importexecution.html.php new file mode 100644 index 0000000000..ca22858247 --- /dev/null +++ b/module/kanban/view/importexecution.html.php @@ -0,0 +1,87 @@ + + * @package kanban + * @version $Id: importexecution.html.php 5090 2022-01-19 14:19:24Z xieqiyu@cnezsoft.com $ + * @link https://www.zentao.net + */ +?> + + + + + +app->rawMethod);?> +
    +
    +
    +

    kanban->importExecution;?>

    +
    +
    +
    +

    kanban->selectedProject;?>

    +
    +
    +
    +

    kanban->selectedLane;?>

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + idAB;?> +
    execution->name;?>execution->status;?>execution->owner;?>execution->end;?>execution->totalEstimate;?>execution->totalConsumed;?>execution->totalLeft;?>
    +
    + + +
    + id);?> +
    id", $execution->name, '', "class='iframe'", true, true);?>name;?>execution->statusList, $execution->status);?>PM);?>end;?>hours->totalEstimate;?>hours->totalConsumed;?>hours->totalLeft;?>
    + + + +
    +
    + diff --git a/module/kanban/view/importplan.html.php b/module/kanban/view/importplan.html.php new file mode 100644 index 0000000000..813f86fe58 --- /dev/null +++ b/module/kanban/view/importplan.html.php @@ -0,0 +1,87 @@ + + * @package kanban + * @version $Id: importplan.html.php 5090 2022-01-19 14:19:24Z xieqiyu@cnezsoft.com $ + * @link https://www.zentao.net + */ +?> + + + + + +app->rawMethod);?> +
    +
    +
    +

    kanban->importPlan;?>

    +
    +
    +
    +

    kanban->selectedProduct;?>

    +
    +
    +
    +

    kanban->selectedLane;?>

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + idAB;?> +
    productplan->title;?>productplan->begin;?>productplan->end;?>productplan->stories;?>productplan->bugs;?>productplan->hour;?>productplan->desc;?>
    +
    + + +
    + id);?> +
    id", $plan->title, '', "class='iframe'", true, true);?>title;?>begin;?>end;?>stories;?>bugs;?>hour;?>desc;?>
    + + + +
    +
    + diff --git a/module/kanban/view/importrelease.html.php b/module/kanban/view/importrelease.html.php new file mode 100644 index 0000000000..e9bf78dfac --- /dev/null +++ b/module/kanban/view/importrelease.html.php @@ -0,0 +1,81 @@ + + * @package kanban + * @version $Id: importrelease.html.php 5090 2022-01-19 14:19:24Z xieqiyu@cnezsoft.com $ + * @link https://www.zentao.net + */ +?> + + + + + +app->rawMethod);?> +
    +
    +
    +

    kanban->importRelease;?>

    +
    +
    +
    +

    kanban->selectedProduct;?>

    +
    +
    +
    +

    kanban->selectedLane;?>

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + idAB;?> +
    release->name;?>release->project;?>release->build;?>release->date;?>
    +
    + + +
    + id);?> +
    id", $release->name, '', "class='iframe'", true, true);?>name;?>projectName;?>buildName;?>date;?>
    + + + +
    +
    + diff --git a/module/kanban/view/space.html.php b/module/kanban/view/space.html.php index f941486817..5b4f0d29a6 100644 --- a/module/kanban/view/space.html.php +++ b/module/kanban/view/space.html.php @@ -50,7 +50,7 @@
    status == 'closed' ? 'disabled' : '';?> - status != 'closed' and $browseType != 'involved') common::printLink('kanban', 'create', "spaceID={$space->id}&type={$space->type}", ' ' . $lang->kanban->create, '', "class='iframe' data-width='75%'", '', true);?> + status != 'closed' and $browseType != 'involved' and !empty($unclosedSpace)) common::printLink('kanban', 'create', "spaceID={$space->id}&type={$space->type}", ' ' . $lang->kanban->create, '', "class='iframe' data-width='75%'", '', true);?> id}", ' ' . $lang->kanban->setting, '', "class='iframe' data-width='75%'", '', true);?> id}", ' ' . $lang->close, '', "class='iframe {$class}'", '', true);?> id}", ' ' . $lang->delete, 'hiddenwin', '', '', true);?> diff --git a/module/kanban/view/view.html.php b/module/kanban/view/view.html.php index c7ed469b92..a63744238e 100644 --- a/module/kanban/view/view.html.php +++ b/module/kanban/view/view.html.php @@ -14,6 +14,7 @@ loadLang('execution'); $laneCount = 0; foreach($regions as $region) $laneCount += $region->laneCount; @@ -23,6 +24,7 @@ js::set('kanbanLang', $lang->kanban); js::set('kanbanlaneLang', $lang->kanbanlane); js::set('kanbancolumnLang', $lang->kanbancolumn); js::set('kanbancardLang', $lang->kanbancard); +js::set('executionLang', $lang->execution); js::set('kanbanID', $kanban->id); js::set('laneCount', $laneCount); js::set('userList', $userList); diff --git a/module/release/model.php b/module/release/model.php index 95870538ea..110c43c63a 100644 --- a/module/release/model.php +++ b/module/release/model.php @@ -47,10 +47,11 @@ class releaseModel extends model * @param int $branch * @param string $type * @param string $orderBy + * @param object $pager * @access public * @return array */ - public function getList($productID, $branch = 'all', $type = 'all', $orderBy = 't1.date_desc') + public function getList($productID, $branch = 'all', $type = 'all', $orderBy = 't1.date_desc', $pager = null) { return $this->dao->select('t1.*, t2.name as productName, t3.id as buildID, t3.name as buildName, t3.project, t4.name as projectName') ->from(TABLE_RELEASE)->alias('t1') @@ -62,6 +63,7 @@ class releaseModel extends model ->beginIF($type != 'all')->andWhere('t1.status')->eq($type)->fi() ->andWhere('t1.deleted')->eq(0) ->orderBy($orderBy) + ->page($pager) ->fetchAll(); } @@ -480,13 +482,13 @@ class releaseModel extends model } /** - * Judge btn is clickable or not. - * - * @param int $release - * @param string $action + * Judge btn is clickable or not. + * + * @param int $release + * @param string $action * @static * @access public - * @return bool + * @return bool */ public static function isClickable($release, $action) { @@ -498,7 +500,7 @@ class releaseModel extends model /** * Send mail to release related users. - * + * * @param int $releaseID * @access public * @return void diff --git a/module/todo/model.php b/module/todo/model.php index 5473018c90..f565eb985b 100644 --- a/module/todo/model.php +++ b/module/todo/model.php @@ -51,7 +51,7 @@ class todoModel extends model if($todo->pri == 'low') $todo->pri = 3; } - if($todo->type != 'custom') + if($todo->type != 'custom' and $todo->idvalue) { $type = $todo->type; $object = $this->loadModel($type)->getByID($this->post->$type);