diff --git a/db/update16.1.sql b/db/update16.1.sql
new file mode 100644
index 0000000000..f759fffb73
--- /dev/null
+++ b/db/update16.1.sql
@@ -0,0 +1,15 @@
+CREATE TABLE `zt_kanbancardgroup` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `kanban` mediumint(8) NOT NULL,
+ `objectType` char(30) NOT NULL,
+ `objectID` mediumint(8) NOT NULL,
+ `lane` mediumint(8) NOT NULL,
+ `column` mediumint(8) NOT NULL,
+ `order` mediumint(8) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `card_group` (`kanban`,`objectType`,`objectID`,`lane`,`column`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+ALTER TABLE `zt_kanban` ADD `displayCards` smallint(6) NOT NULL AFTER `order`;
+ALTER TABLE `zt_kanbancard` DROP COLUMN `lane`;
+ALTER TABLE `zt_kanbancard` DROP COLUMN `column`;
diff --git a/db/zentao.sql b/db/zentao.sql
index c733278613..f687d2b7be 100644
--- a/db/zentao.sql
+++ b/db/zentao.sql
@@ -677,6 +677,7 @@ CREATE TABLE `zt_kanban` (
`archived` enum('0', '1') NOT NULL DEFAULT '0',
`status` enum('active','closed') NOT NULL default 'active',
`order` mediumint(8) NOT NULL DEFAULT '0',
+ `displayCards` smallint(6) NOT NULL,
`createdBy` char(30) NOT NULL,
`createdDate` datetime NOT NULL,
`lastEditedBy` char(30) NOT NULL,
@@ -731,6 +732,18 @@ CREATE TABLE `zt_kanbancard` (
`deleted` enum('0', '1') NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+-- DROP TABLE IF EXISTS `zt_kanbancardgroup`;
+CREATE TABLE `zt_kanbancardgroup` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `kanban` mediumint(8) NOT NULL,
+ `objectType` char(30) NOT NULL,
+ `objectID` mediumint(8) NOT NULL,
+ `lane` mediumint(8) NOT NULL,
+ `column` mediumint(8) NOT NULL,
+ `order` mediumint(8) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `card_group` (`kanban`,`objectType`,`objectID`,`lane`,`column`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_kanbangroup`;
CREATE TABLE `zt_kanbangroup` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
diff --git a/module/common/lang/menu.php b/module/common/lang/menu.php
index 3bf77d2863..95959733ca 100644
--- a/module/common/lang/menu.php
+++ b/module/common/lang/menu.php
@@ -289,6 +289,11 @@ $lang->waterfall->menu->design['subMenu']->dbds = array('link' => "{$lang->d
$lang->waterfall->menu->design['subMenu']->ads = array('link' => "{$lang->design->ADS}|design|browse|projectID=%s&productID=0&browseType=ADS");
$lang->waterfall->menu->design['subMenu']->bysearch = array('link' => ' ' . $lang->searchAB . '');
+/* Kanban project menu. */
+$lang->kanban->menu = new stdclass();
+$lang->kanban->menuOrder = array();
+$lang->kanban->dividerMenu = '';
+
/* Execution menu. */
$lang->execution->homeMenu = new stdclass();
$lang->execution->homeMenu->all = array('link' => "{$lang->execution->all}|execution|all|", 'alias' => 'batchedit');
diff --git a/module/common/model.php b/module/common/model.php
index fe1993ad7a..814602a66d 100644
--- a/module/common/model.php
+++ b/module/common/model.php
@@ -528,7 +528,7 @@ class commonModel extends model
$attr = "class='iframe' data-width='650px'";
break;
case 'project':
- if(isset($config->maxVersion) and!defined('TUTORIAL'))
+ if(!defined('TUTORIAL'))
{
$params = "programID=0©ProjectID=0&extra=from=global";
$createMethod = 'createGuide';
diff --git a/module/execution/model.php b/module/execution/model.php
index 549b58d0d2..e6663c158b 100644
--- a/module/execution/model.php
+++ b/module/execution/model.php
@@ -2478,6 +2478,23 @@ class executionModel extends model
->fetchAll('account');
}
+ /**
+ * Get members by execution id list.
+ *
+ * @param array $executionIdList
+ * @access public
+ * @return void
+ */
+ public function getMembersByIdList($executionIdList)
+ {
+ return $this->dao->select("t1.root, t1.account, t2.realname")->from(TABLE_TEAM)->alias('t1')
+ ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account = t2.account')
+ ->where('t1.root')->in($executionIdList)
+ ->andWhere('t1.type')->eq('execution')
+ ->andWhere('t2.deleted')->eq('0')
+ ->fetchGroup('root');
+ }
+
/**
* Get the skip members of the team.
*
diff --git a/module/project/control.php b/module/project/control.php
index fb5a0cfbce..2851aa5228 100644
--- a/module/project/control.php
+++ b/module/project/control.php
@@ -222,10 +222,11 @@ class project extends control
* Project index view.
*
* @param int $projectID
+ * @param string $browseType
* @access public
* @return void
*/
- public function index($projectID = 0)
+ public function index($projectID = 0, $browseType = 'all')
{
$projectID = $this->project->saveState($projectID, $this->project->getPairsByProgram());
@@ -240,6 +241,15 @@ class project extends control
if(!$projectID) $this->locate($this->createLink('project', 'browse'));
setCookie("lastProject", $projectID, $this->config->cookieLife, $this->config->webRoot, '', false, true);
+ if($project->model == 'kanban')
+ {
+ $kanbanList = $this->loadModel('execution')->getList($projectID, 'all', $browseType);
+ $this->view->kanbanList = $kanbanList;
+ $this->view->browseType = $browseType;
+ $this->view->memberGroup = $this->execution->getMembersByIdList(array_keys($kanbanList));
+ $this->view->usersAvatar = $this->loadModel('user')->getAvatarPairs();
+ }
+
$this->view->title = $this->lang->project->common . $this->lang->colon . $this->lang->project->index;
$this->view->position[] = $this->lang->project->index;
$this->view->project = $project;
diff --git a/module/project/css/index.css b/module/project/css/index.css
new file mode 100644
index 0000000000..c32fcc5b4f
--- /dev/null
+++ b/module/project/css/index.css
@@ -0,0 +1,24 @@
+#cards > .col {width: 25% !important;}
+#cards .panel {margin: 0 0; border: 1px solid #DCDCDC; border-radius: 4px; box-shadow: none; cursor: pointer; height: 160px; margin-bottom: 5px;}
+#cards .panel:hover {border-color: #006AF1; box-shadow: 0 0 10px 0 rgba(0,0,100,.25);}
+#cards .panel-heading {padding: 4px 2px 10px 16px;}
+#cards .kanban-name {overflow: hidden; white-space:nowrap; width: 88%; float: left; padding-top: 8px;}
+#cards .panel .label-closed {background: #AAA !important; color: #FFF; margin-top:-1px !important;}
+#cards .kanban-actions {float: right; visibility: hidden;}
+#cards .kanban-actions .dropdown-menu.pull-right {top:31px; right: -84px; left: auto;}
+#cards .kanban-actions .dropdown-menu.pull-left {top:31px; right:-1px; left: auto;}
+#cards .panel .label-closed {background: #AAA !important; color: #FFF; margin-top:-6px;}
+#cards .panel-body {padding: 0 16px 16px;}
+#cards .kanban-desc {color: #838a9d; word-break:break-all; height: 70px; overflow: hidden; display: -webkit-box; float:left;}
+#cards .kanban-footer {position: absolute; bottom: 10px; right: 10px; left: 15px;}
+#cards .kanban-members {float: left; height: 24px; line-height: 24px;}
+#cards .kanban-members > div {display: inline-block; height: 24px;}
+#cards .kanban-members > div + div {margin-left: -5px;}
+#cards .kanban-members > div > .avatar {display: inline-block; width: 24px; height: 24px; line-height: 24px; margin-right: 1px;}
+#cards .kanban-members > span {display: inline-block; color: transparent; width: 2px; height: 2px; background-color: #8990a2; position: relative; border-radius: 50%; top: 3px; margin: 0 3px;}
+#cards .kanban-members > span:before,
+#cards .kanban-members > span:after {content: ''; display: block; position: absolute; width: 2px; height: 2px; background-color: #8990a2; top: 0; border-radius: 50%}
+#cards .kanban-members > span:before {left: -4px;}
+#cards .kanban-members > span:after {right: -4px;}
+#cards .kanban-members-total {display: inline-block; margin-left: 6px; position: relative; top: 3px}
+#cards .kanbanAcl {position: absolute; right: 0px; bottom: 2px; color: #838a9d;}
diff --git a/module/project/lang/en.php b/module/project/lang/en.php
index 2df5ddb34c..bcc353b63e 100644
--- a/module/project/lang/en.php
+++ b/module/project/lang/en.php
@@ -155,6 +155,8 @@ $lang->project->etc = " , etc";
$lang->project->product = 'Product';
$lang->project->branch = 'Branch';
$lang->project->plan = 'Plan';
+$lang->project->kanban = 'Kanban';
+$lang->project->createKanban = 'Create Kanban';
/* Project Kanban. */
$lang->project->typeList = array();
@@ -220,6 +222,7 @@ $lang->project->currencySymbol['SGD'] = 'S$';
$lang->project->modelList['scrum'] = "Scrum";
$lang->project->modelList['waterfall'] = "CMMI";
+$lang->project->modelList['kanban'] = "Kanban";
$lang->project->featureBar['all'] = 'All';
$lang->project->featureBar['doing'] = 'Going';
@@ -252,6 +255,10 @@ $lang->project->endList[186] = 'Half yearly';
$lang->project->endList[365] = 'One year';
$lang->project->endList[999] = 'Longtime';
+$lang->project->scrumTitle = 'Agile Development Management';
+$lang->project->waterfallTitle = 'Waterfall Project Management';
+$lang->project->kanbanTitle = 'Kanban Project Management';
+
$lang->project->empty = 'No project.';
$lang->project->nextStep = 'Next step';
$lang->project->hoursUnit = '%s hours';
@@ -260,7 +267,6 @@ $lang->project->lastIteration = "Recent {$lang->executionCommon}";
$lang->project->ongoingStage = 'Ongoing stage';
$lang->project->scrum = 'Scrum';
$lang->project->waterfall = 'Waterfall';
-$lang->project->waterfallTitle = 'Waterfall Project Management';
$lang->project->cannotCreateChild = 'It is not empty, so you cannot add a child. You can add a parent for it, and then create a child.';
$lang->project->confirmDelete = "Do you want to delete [%s]?";
$lang->project->emptyPM = 'No manager';
@@ -278,7 +284,6 @@ $lang->project->programTitle['end'] = 'End-level project only';
$lang->project->accessDenied = 'Access denied to this project';
$lang->project->chooseProgramType = 'Select management type';
-$lang->project->scrumTitle = 'Agile Development Management';
$lang->project->cannotCreateChild = 'The project has contents, so you cannot add a child project. You can create a parent project for this one and then add a child project for the parent project.';
$lang->project->hasChildren = 'This project has a child project, so it cannot be deleted.';
$lang->project->confirmDelete = 'Do you want to delete this project?';
diff --git a/module/project/lang/zh-cn.php b/module/project/lang/zh-cn.php
index 643d7f02d8..c0e8a48e9b 100644
--- a/module/project/lang/zh-cn.php
+++ b/module/project/lang/zh-cn.php
@@ -155,6 +155,8 @@ $lang->project->etc = "等";
$lang->project->product = '所属产品';
$lang->project->branch = '所属分支';
$lang->project->plan = '所属计划';
+$lang->project->createKanban = '创建看板';
+$lang->project->kanban = '看板';
/* Project Kanban. */
$lang->project->typeList = array();
@@ -220,6 +222,7 @@ $lang->project->currencySymbol['SGD'] = 'S$';
$lang->project->modelList['scrum'] = "Scrum";
$lang->project->modelList['waterfall'] = "瀑布";
+$lang->project->modelList['kanban'] = "看板";
$lang->project->featureBar['all'] = '所有';
$lang->project->featureBar['doing'] = '进行中';
@@ -252,6 +255,10 @@ $lang->project->endList[186] = '半年';
$lang->project->endList[365] = '一年';
$lang->project->endList[999] = '长期';
+$lang->project->scrumTitle = '敏捷开发全流程项目管理';
+$lang->project->waterfallTitle = '瀑布式项目管理';
+$lang->project->kanbanTitle = '专业研发看板项目管理';
+
$lang->project->empty = '暂时没有项目';
$lang->project->nextStep = '下一步';
$lang->project->hoursUnit = '%s 工时';
@@ -260,7 +267,6 @@ $lang->project->lastIteration = "近期{$lang->executionCommon}";
$lang->project->ongoingStage = '进行中的阶段';
$lang->project->scrum = 'Scrum';
$lang->project->waterfall = '瀑布';
-$lang->project->waterfallTitle = '瀑布式项目管理';
$lang->project->cannotCreateChild = '该项目已经有实际的内容,无法直接添加子项目。您可以为当前项目创建一个父项目,然后在新的父项目下面添加子项目。';
$lang->project->confirmDelete = "您确定要删除吗?";
$lang->project->emptyPM = '暂无';
@@ -278,7 +284,6 @@ $lang->project->programTitle['end'] = '只显示最后一级项目集';
$lang->project->accessDenied = '您无权访问该项目!';
$lang->project->chooseProgramType = '选择项目管理方式';
-$lang->project->scrumTitle = '敏捷开发全流程项目管理';
$lang->project->cannotCreateChild = '该项目已经有实际的内容,无法直接添加子项目。您可以为当前项目创建一个父项目,然后在新的父项目下面添加子项目。';
$lang->project->hasChildren = '该项目有子项目存在,不能删除。';
$lang->project->confirmDelete = "您确定删除项目[%s]吗?";
diff --git a/module/project/model.php b/module/project/model.php
index c01a4e8c2c..d47fb02ab0 100644
--- a/module/project/model.php
+++ b/module/project/model.php
@@ -632,7 +632,7 @@ class projectModel extends model
/**
* Get project pairs by model and project.
*
- * @param string $model all|scrum|waterfall
+ * @param string $model all|scrum|waterfall|kanban
* @param int $programID
* @access public
* @return array
@@ -1776,7 +1776,7 @@ class projectModel extends model
$type = 'project';
if($this->config->systemMode == 'new')
{
- if($project->type == 'sprint' or $project->type == 'stage') $type = 'execution';
+ if($project->type == 'sprint' or $project->type == 'stage' or $project->type == 'kanban') $type = 'execution';
}
$members = $this->dao->select("t1.account, if(t2.deleted='0', t2.realname, t1.account) as realname")->from(TABLE_TEAM)->alias('t1')
diff --git a/module/project/view/ajaxgetdropmenu.html.php b/module/project/view/ajaxgetdropmenu.html.php
index 4aa753f687..648ed20b7f 100644
--- a/module/project/view/ajaxgetdropmenu.html.php
+++ b/module/project/view/ajaxgetdropmenu.html.php
@@ -72,9 +72,12 @@ foreach($projects as $programID => $programProjects)
foreach($programProjects as $index => $project)
{
- $selected = $project->id == $projectID ? 'selected' : '';
- $link = helper::createLink('project', 'index', "projectID=%s", '', '', $project->id);
- $projectName = $project->model == 'scrum' ? ' ' . $project->name : ' ' . $project->name;
+ $selected = $project->id == $projectID ? 'selected' : '';
+ $link = helper::createLink('project', 'index', "projectID=%s", '', '', $project->id);
+ $icon = ' ';
+
+ if($project->model != 'scrum') $icon = " ";
+ $projectName = $icon . $project->name;
if($project->status != 'done' and $project->status != 'closed' and $project->PM == $this->app->user->account)
{
diff --git a/module/project/view/createguide.html.php b/module/project/view/createguide.html.php
index 42f5a457b9..ee90dc000c 100644
--- a/module/project/view/createguide.html.php
+++ b/module/project/view/createguide.html.php
@@ -15,20 +15,27 @@
project->chooseProgramType; ?>
-
+
createLink("project", "create", "model=scrum&programID=$programID©ProjectID=0&extra=productID=$productID,branchID=$branchID"), "

", '', "data-app='project' class='createButton'")?>
project->scrum; ?>
project->scrumTitle; ?>
-
+
createLink("project", "create", "model=waterfall&programID=$programID©ProjectID=0&extra=productID=$productID,branchID=$branchID"), "

", '', "data-app='project' class='createButton'")?>
project->waterfall; ?>
project->waterfallTitle; ?>
+
+
+ createLink("project", "create", "model=kanban&programID=$programID©ProjectID=0&extra=productID=$productID,branchID=$branchID"), "

", '', "data-app='project' class='createButton'")?>
+
project->kanban;?>
+
project->kanbanTitle;?>
+
+
diff --git a/module/project/view/index.html.php b/module/project/view/index.html.php
index a21c11d5f4..83900d0e7e 100644
--- a/module/project/view/index.html.php
+++ b/module/project/view/index.html.php
@@ -10,5 +10,74 @@
*/
?>
+model != 'kanban'):?>
fetch('block', 'dashboard', "module=project&type={$project->model}&projectID={$project->id}");?>
+
+
+
+ project->featureBar as $label => $labelName)
+ {
+ $active = $browseType == $label ? 'btn-active-text' : '';
+ echo html::a($this->createLink('project', 'index', "projectID=$project->id&browseType=" . $label), '' . $labelName . ' ' . ($browseType == $label ? "0" : ''), '', "class='btn btn-link $active'");
+ }
+ ?>
+
+
+ createLink('execution', 'create', "projectID=$project->id"), " " . $lang->project->createKanban, '', "class='btn btn-primary create-execution-btn'");?>
+
+
+
+
+
+
+
+ noData;?>
+ id", ' ' . $lang->project->createKanban, '', 'class="btn btn-info"');?>
+
+
+
+ $kanban):?>
+
+
+
+
+ execution->statusList, $kanban->status);?>
+ name;?>
+
+
+
+
+
+
+
+
+
+