diff --git a/config/config.php b/config/config.php index f8a7993985..fef6e3bc31 100644 --- a/config/config.php +++ b/config/config.php @@ -130,6 +130,7 @@ define('TABLE_TESTRESULT', '`' . $config->db->prefix . 'testresult`'); define('TABLE_USERTPL', '`' . $config->db->prefix . 'usertpl`'); define('TABLE_PRODUCT', '`' . $config->db->prefix . 'product`'); +define('TABLE_BRANCH', '`' . $config->db->prefix . 'branch`'); define('TABLE_STORY', '`' . $config->db->prefix . 'story`'); define('TABLE_STORYSPEC', '`' . $config->db->prefix . 'storyspec`'); define('TABLE_PRODUCTPLAN', '`' . $config->db->prefix . 'productplan`'); diff --git a/db/update7.3.sql b/db/update7.3.sql index e99585ea65..9c80ac0ff4 100644 --- a/db/update7.3.sql +++ b/db/update7.3.sql @@ -1,3 +1,20 @@ ALTER TABLE `zt_action` CHANGE `extra` `extra` text COLLATE 'utf8_general_ci' NOT NULL AFTER `comment`; ALTER TABLE `zt_release` ADD `leftBugs` text COLLATE 'utf8_general_ci' NOT NULL AFTER `bugs`; ALTER TABLE `zt_release` ADD `status` varchar(20) COLLATE 'utf8_general_ci' NOT NULL DEFAULT 'normal' AFTER `desc`; +ALTER TABLE `zt_product` ADD `type` varchar(30) COLLATE 'utf8_general_ci' NOT NULL DEFAULT 'normal' AFTER `code`; + +ALTER TABLE `zt_projectproduct` ADD `branch` mediumint(8) unsigned NOT NULL; +ALTER TABLE `zt_productplan` ADD `branch` mediumint(8) unsigned NOT NULL AFTER `product`; +ALTER TABLE `zt_build` ADD `branch` mediumint(8) unsigned NOT NULL AFTER `product`; +ALTER TABLE `zt_release` ADD `branch` mediumint(8) unsigned NOT NULL AFTER `product`; +ALTER TABLE `zt_bug` ADD `branch` mediumint(8) unsigned NOT NULL AFTER `product`; +ALTER TABLE `zt_case` ADD `branch` mediumint(8) unsigned NOT NULL AFTER `product`; +ALTER TABLE `zt_module` ADD `branch` varchar(50) COLLATE 'utf8_general_ci' unsigned NOT NULL AFTER `root`; +ALTER TABLE `zt_story` ADD `branch` varchar(50) COLLATE 'utf8_general_ci' NOT NULL AFTER `product`; + +CREATE TABLE `zt_branch` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, + `product` mediumint unsigned NOT NULL, + `name` varchar(255) COLLATE 'utf8_general_ci' NOT NULL, + `deleted` enum('0','1') COLLATE 'utf8_general_ci' NOT NULL DEFAULT '0' +); diff --git a/module/branch/control.php b/module/branch/control.php new file mode 100644 index 0000000000..243754caae --- /dev/null +++ b/module/branch/control.php @@ -0,0 +1,51 @@ + + * @package branch + * @version $Id$ + * @link http://www.zentao.net + */ +class branch extends control +{ + public function manage($productID) + { + if($_POST) + { + $this->branch->manage($productID); + die(js::reload('parent')); + } + $this->view->title = $this->lang->branch->manage; + $this->view->position[] = $this->lang->branch->manage; + + $this->loadModel('product')->setMenu($this->product->getPairs('nocode'), $productID); + + $this->view->product = $this->product->getById($productID); + $this->view->branches = $this->branch->getPairs($productID, 'noempty'); + $this->display(); + } + + public function ajaxGetDropMenu($productID, $module, $method, $extra) + { + $this->view->link = $this->loadModel('product')->getProductLink($module, $method, $extra, true); + $this->view->productID = $productID; + $this->view->module = $module; + $this->view->method = $method; + $this->view->extra = $extra; + $this->view->branches = $this->branch->getPairs($productID); + $this->display(); + } + + public function ajaxGetMatchedItems($keywords, $module, $method, $extra, $objectID) + { + $this->view->link = $this->loadModel('product')->getProductLink($module, $method, $extra, true); + $this->view->branches = $this->dao->select('*')->from(TABLE_BRANCH)->where('deleted')->eq(0)->andWhere('product')->eq($objectID)->andWhere('name')->like("%$keywords%")->orderBy('id desc')->fetchPairs('id', 'name'); + $this->view->productID = $objectID; + $this->view->keywords = $keywords; + $this->display(); + } +} + diff --git a/module/branch/lang/zh-cn.php b/module/branch/lang/zh-cn.php new file mode 100644 index 0000000000..180dd612a0 --- /dev/null +++ b/module/branch/lang/zh-cn.php @@ -0,0 +1,4 @@ +branch->manage = '分支管理'; + +$lang->branch->all = '所有'; diff --git a/module/branch/model.php b/module/branch/model.php new file mode 100644 index 0000000000..08d6c74f04 --- /dev/null +++ b/module/branch/model.php @@ -0,0 +1,41 @@ + + * @package branch + * @version $Id$ + * @link http://www.zentao.net + */ +class branchModel extends model +{ + public function getPairs($productID, $params = '') + { + $branches = $this->dao->select('*')->from(TABLE_BRANCH)->where('product')->eq($productID)->andWhere('deleted')->eq(0)->orderBy('id_desc')->fetchPairs('id', 'name'); + if(strpos($params, 'noempty') === false) $branches = array('0' => $this->lang->branch->all) + $branches; + return $branches; + } + + public function manage($productID) + { + $oldBranches = $this->getPairs($productID, 'noempty'); + $data = fixer::input('post')->get(); + if(isset($data->branch)) + { + foreach($data->branch as $branchID => $branch) + { + if($oldBranches[$branchID] != $branch) $this->dao->update(TABLE_BRANCH)->set('name')->eq($branch)->where('id')->eq($branchID)->exec(); + } + } + foreach($data->newbranch as $branch) + { + if(empty($branch)) continue; + $this->dao->insert(TABLE_BRANCH)->set('name')->eq($branch)->set('product')->eq($productID)->exec(); + } + + return dao::isError(); + } +} + diff --git a/module/branch/view/ajaxgetdropmenu.html.php b/module/branch/view/ajaxgetdropmenu.html.php new file mode 100644 index 0000000000..8a270cd345 --- /dev/null +++ b/module/branch/view/ajaxgetdropmenu.html.php @@ -0,0 +1,17 @@ + + + + +search->common;?>'/> +
| idAB);?> | priAB);?> | story->title);?> | diff --git a/module/product/view/create.html.php b/module/product/view/create.html.php index 04cb35cbfe..59dc8c93da 100644 --- a/module/product/view/create.html.php +++ b/module/product/view/create.html.php @@ -41,6 +41,10 @@product->RD;?> | ||
|---|---|---|---|---|---|
| product->type;?> | +product->typeList, 'normal', "class='form-control'");?> | + | |||
| product->desc;?> | diff --git a/module/product/view/edit.html.php b/module/product/view/edit.html.php index 201494a4bf..d665866660 100644 --- a/module/product/view/edit.html.php +++ b/module/product/view/edit.html.php @@ -42,6 +42,10 @@ | product->RD;?> | RD, "class='form-control chosen'");?> | ||
| product->type;?> | +product->typeList, $product->type, "class='form-control'");?> | + | |||
| product->status;?> | product->statusList, $product->status, "class='form-control'");?> | diff --git a/module/product/view/view.html.php b/module/product/view/view.html.php index ca66c7814c..21a25cfe5f 100644 --- a/module/product/view/view.html.php +++ b/module/product/view/view.html.php @@ -81,6 +81,10 @@ | product->RD;?> | RD];?> | |
| product->type;?> | +product->typeList[$product->type];?> | + | |||
| product->status;?> | product->statusList[$product->status];?> | diff --git a/module/productplan/control.php b/module/productplan/control.php index 1fe52294b5..4f0b3a7115 100644 --- a/module/productplan/control.php +++ b/module/productplan/control.php @@ -290,7 +290,7 @@ class productplan extends control } else { - $allStories = $this->story->getProductStories($this->view->product->id, $moduleID = '0', $status = 'draft,active,changed'); + $allStories = $this->story->getProductStories($this->view->product->id, $plan->branch, $moduleID = '0', $status = 'draft,active,changed'); } $this->view->allStories = $allStories; diff --git a/module/project/model.php b/module/project/model.php index a5b16ad328..fb7d5ed343 100644 --- a/module/project/model.php +++ b/module/project/model.php @@ -551,7 +551,7 @@ class projectModel extends model * @access public * @return array */ - public function getList($status = 'all', $limit = 0, $productID = 0) + public function getList($status = 'all', $limit = 0, $productID = 0, $branch = 0) { if($productID != 0) { @@ -563,6 +563,7 @@ class projectModel extends model ->andWhere('t2.deleted')->eq(0) ->andWhere('t2.iscat')->eq(0) ->beginIF($status == 'undone')->andWhere('t2.status')->ne('done')->fi() + ->beginIF($branch != 0)->andWhere('t1.branch')->like(",$branch,")->fi() ->beginIF($status == 'isdoing')->andWhere('t2.status')->ne('done')->andWhere('t2.status')->ne('suspended')->fi() ->beginIF($status != 'all' and $status != 'isdoing' and $status != 'undone')->andWhere('status')->in($status)->fi() ->orderBy('order_desc') @@ -630,10 +631,10 @@ class projectModel extends model * @access public * @return void */ - public function getProjectStats($status = 'undone', $productID = 0, $itemCounts = 30, $orderBy = 'order_desc', $pager = null) + public function getProjectStats($status = 'undone', $productID = 0, $branch, $itemCounts = 30, $orderBy = 'order_desc', $pager = null) { /* Init vars. */ - $projects = $this->getList($status, 0, $productID); + $projects = $this->getList($status, 0, $productID, $branch); foreach($projects as $projectID => $project) { if(!$this->checkPriv($project)) unset($projects[$projectID]); diff --git a/module/story/model.php b/module/story/model.php index 771b3467eb..1f8502f1e1 100644 --- a/module/story/model.php +++ b/module/story/model.php @@ -926,14 +926,15 @@ class storyModel extends model * @access public * @return array */ - public function getProductStories($productID = 0, $moduleIds = 0, $status = 'all', $orderBy = 'id_desc', $pager = null) + public function getProductStories($productID = 0, $branch = 0, $moduleIds = 0, $status = 'all', $orderBy = 'id_desc', $pager = null) { return $this->dao->select('t1.*, t2.title as planTitle') ->from(TABLE_STORY)->alias('t1') ->leftJoin(TABLE_PRODUCTPLAN)->alias('t2')->on('t1.plan = t2.id') ->where('t1.product')->in($productID) - ->beginIF(!empty($moduleIds))->andWhere('module')->in($moduleIds)->fi() - ->beginIF($status and $status != 'all')->andWhere('status')->in($status)->fi() + ->beginIF(!empty($branch))->andWhere("CONCAT(',', t1.branch, ',')")->like("%,$branch,%")->fi() + ->beginIF(!empty($moduleIds))->andWhere('t1.module')->in($moduleIds)->fi() + ->beginIF($status and $status != 'all')->andWhere('t1.status')->in($status)->fi() ->andWhere('t1.deleted')->eq(0) ->orderBy($orderBy)->page($pager)->fetchAll(); } @@ -974,9 +975,9 @@ class storyModel extends model * @access public * @return array */ - public function getByAssignedTo($productID, $account, $orderBy, $pager) + public function getByAssignedTo($productID, $branch, $account, $orderBy, $pager) { - return $this->getByField($productID, 'assignedTo', $account, $orderBy, $pager); + return $this->getByField($productID, $branch, 'assignedTo', $account, $orderBy, $pager); } /** @@ -989,9 +990,9 @@ class storyModel extends model * @access public * @return array */ - public function getByOpenedBy($productID, $account, $orderBy, $pager) + public function getByOpenedBy($productID, $branch, $account, $orderBy, $pager) { - return $this->getByField($productID, 'openedBy', $account, $orderBy, $pager); + return $this->getByField($productID, $branch, 'openedBy', $account, $orderBy, $pager); } /** @@ -1004,9 +1005,9 @@ class storyModel extends model * @access public * @return array */ - public function getByReviewedBy($productID, $account, $orderBy, $pager) + public function getByReviewedBy($productID, $branch, $account, $orderBy, $pager) { - return $this->getByField($productID, 'reviewedBy', $account, $orderBy, $pager, 'include'); + return $this->getByField($productID, $branch, 'reviewedBy', $account, $orderBy, $pager, 'include'); } /** @@ -1018,9 +1019,9 @@ class storyModel extends model * @param object $pager * @return array */ - public function getByClosedBy($productID, $account, $orderBy, $pager) + public function getByClosedBy($productID, $branch, $account, $orderBy, $pager) { - return $this->getByField($productID, 'closedBy', $account, $orderBy, $pager); + return $this->getByField($productID, $branch, 'closedBy', $account, $orderBy, $pager); } /** @@ -1033,9 +1034,9 @@ class storyModel extends model * @access public * @return array */ - public function getByStatus($productID, $status, $orderBy, $pager) + public function getByStatus($productID, $branch, $status, $orderBy, $pager) { - return $this->getByField($productID, 'status', $status, $orderBy, $pager); + return $this->getByField($productID, $branch, 'status', $status, $orderBy, $pager); } /** @@ -1050,13 +1051,14 @@ class storyModel extends model * @access public * @return array */ - public function getByField($productID, $fieldName, $fieldValue, $orderBy, $pager, $operator = 'equal') + public function getByField($productID, $branch, $fieldName, $fieldValue, $orderBy, $pager, $operator = 'equal') { return $this->dao->select('t1.*, t2.title as planTitle') ->from(TABLE_STORY)->alias('t1') ->leftJoin(TABLE_PRODUCTPLAN)->alias('t2')->on('t1.plan = t2.id') ->where('t1.product')->in($productID) ->andWhere('t1.deleted')->eq(0) + ->beginIF(!empty($branch))->andWhere("CONCAT(',', t1.branch, ',')")->like("%,$branch,%")->fi() ->beginIF($operator == 'equal')->andWhere($fieldName)->eq($fieldValue)->fi() ->beginIF($operator == 'include')->andWhere($fieldName)->like("%$fieldValue%")->fi() ->orderBy($orderBy) @@ -1073,7 +1075,7 @@ class storyModel extends model * @access public * @return array */ - public function getWillClose($productID, $orderBy, $pager) + public function getWillClose($productID, $branch, $orderBy, $pager) { return $this->dao->select('t1.*, t2.title as planTitle') ->from(TABLE_STORY)->alias('t1') @@ -1099,7 +1101,7 @@ class storyModel extends model * @access public * @return array */ - public function getBySearch($productID, $queryID, $orderBy, $pager = null, $projectID = '') + public function getBySearch($productID, $branch, $queryID, $orderBy, $pager = null, $projectID = '') { if($projectID != '') { @@ -1367,7 +1369,7 @@ class storyModel extends model */ public function getZeroCase($productID, $orderBy = 'id_desc') { - $allStories = $this->getProductStories($productID, 0, 'all', $orderBy); + $allStories = $this->getProductStories($productID, 0, 0, 'all', $orderBy); $casedStories = $this->dao->select('DISTINCT story')->from(TABLE_CASE)->where('product')->eq($productID)->andWhere('story')->ne(0)->andWhere('deleted')->eq(0)->fetchAll('story'); foreach($allStories as $key => $story) diff --git a/www/js/my.full.js b/www/js/my.full.js index b651f1ff01..3fc58f0412 100644 --- a/www/js/my.full.js +++ b/www/js/my.full.js @@ -93,10 +93,11 @@ function shortcut() */ function showDropMenu(objectType, objectID, module, method, extra) { - var li = $('#currentItem').closest('li'); + var itemID = objectType == 'branch' ? '#currentBranch' : '#currentItem'; + var li = $(itemID).closest('li'); if(li.hasClass('show')) {li.removeClass('show'); return;} - var $dropMenu = $('#dropMenu'); + var $dropMenu = li.find('#dropMenu'); if(!li.data('showagain')) { li.data('showagain', true); @@ -105,7 +106,7 @@ function showDropMenu(objectType, objectID, module, method, extra) $dropMenu.on('keydown', '#search', function(e){ var code = e.which; - var $this = $('#searchResult > .search-list > ul > li.active'); + var $this = $dropMenu.find('#searchResult > .search-list > ul > li.active'); if(code === 38) // up { $this.removeClass('active'); @@ -122,7 +123,7 @@ function showDropMenu(objectType, objectID, module, method, extra) return; } } - $('#searchResult > .search-list > ul > li:not(.heading):last').addClass('active'); + $dropMenu.find('#searchResult > .search-list > ul > li:not(.heading):last').addClass('active'); } else if(code === 40) // down { @@ -140,7 +141,7 @@ function showDropMenu(objectType, objectID, module, method, extra) return; } } - $('#searchResult > .search-list > ul > li:not(.heading):first').addClass('active'); + $dropMenu.find('#searchResult > .search-list > ul > li:not(.heading):first').addClass('active'); } else if(code === 13) // enter { @@ -158,14 +159,14 @@ function showDropMenu(objectType, objectID, module, method, extra) searchItems(searchKey, objectType, objectID, module, method, extra); }, 200)); }).on('mouseenter', '#searchResult .search-list > ul > li', function(){ - $('#searchResult > .search-list > ul > li.active').removeClass('active'); + $dropMenu.find('#searchResult > .search-list > ul > li.active').removeClass('active'); $(this).addClass('active'); }); } $.get(createLink(objectType, 'ajaxGetDropMenu', "objectID=" + objectID + "&module=" + module + "&method=" + method + "&extra=" + extra), function(data) { $dropMenu.html(data).find('#search').focus(); - $('#searchResult > .search-list > ul > li:not(.heading)').removeClass('active').first().addClass('active'); + $dropMenu.find('#searchResult > .search-list > ul > li:not(.heading)').removeClass('active').first().addClass('active'); }); li.addClass('show'); @@ -184,11 +185,14 @@ function showDropMenu(objectType, objectID, module, method, extra) */ function showDropResult(objectType, objectID, module, method, extra) { + var itemID = objectType == 'branch' ? '#currentBranch' : '#currentItem'; + var li = $(itemID).closest('li'); + var $dropMenu = li.find('#dropMenu'); $.get(createLink(objectType, 'ajaxGetDropMenu', "objectID=" + objectID + "&module=" + module + "&method=" + method + "&extra=" + extra), function(data) { - $('#dropMenu').html(data); - setTimeout(function(){$("#dropMenu #search").focus();}, 200); - $('#searchResult > .search-list > ul > li:not(.heading)').removeClass('active').first().addClass('active'); + $dropMenu.html(data); + setTimeout(function(){$dropMenu.find("#search").focus();}, 200); + $dropMenu.find('#searchResult > .search-list > ul > li:not(.heading)').removeClass('active').first().addClass('active'); }); } @@ -213,10 +217,13 @@ function searchItems(keywords, objectType, objectID, module, method, extra) } else { + var itemID = objectType == 'branch' ? '#currentBranch' : '#currentItem'; + var li = $(itemID).closest('li'); + var $dropMenu = li.find('#dropMenu'); keywords = encodeURI(keywords); - if(keywords != '-') $.get(createLink(objectType, 'ajaxGetMatchedItems', "keywords=" + keywords + "&module=" + module + "&method=" + method + "&extra=" + extra), function(data) + if(keywords != '-') $.get(createLink(objectType, 'ajaxGetMatchedItems', "keywords=" + keywords + "&module=" + module + "&method=" + method + "&extra=" + extra + "&objectID=" + objectID), function(data) { - $('#searchResult').html(data).find('.search-list > ul > li:not(.heading)').removeClass('active').first().addClass('active'); + $dropMenu.find('#searchResult').html(data).find('.search-list > ul > li:not(.heading)').removeClass('active').first().addClass('active'); }); } }||||