diff --git a/Makefile b/Makefile
index 9f8a28d0a1..cc8fefd074 100644
--- a/Makefile
+++ b/Makefile
@@ -108,6 +108,7 @@ zentaoxx:
sed -i "s/\$$super = \$$this->dao->select('admin')->from(TABLE_USER)->where('id')->eq(\$$userID)->fetch('admin');/\$$account = \$$this->dao->select('account')->from(TABLE_USER)->where('id')->eq(\$$userID)->fetch('account');\n\$$admins = \$$this->dao->select('admins')->from(TABLE_COMPANY)->where('id')->eq(\$$this->app->company->id)->fetch('admins');\n\$$adminArray = explode(',', \$$admins);\n\$$super = in_array(\$$account, \$$adminArray) ? 'super' : '';/g" zentaoxx/extension/xuan/im/control.php
sed -i "/foreach(\$$users as \$$user)/i \$$admins = \$$this->dao->select('admins')->from(TABLE_COMPANY)->where('id')->eq(\$$this->app->company->id)->fetch('admins');\$$adminArray = explode(',', \$$admins);" zentaoxx/extension/xuan/im/model/user.php
sed -i "/if(\!isset(\$$user->signed)) \$$user->signed = 0;/a \$$user->admin = in_array(\$$user->account, \$$adminArray) ? 'super' : '';" zentaoxx/extension/xuan/im/model/user.php
+ sed -i "/updateUser->ping/d" zentaoxx/extension/xuan/im/model/user.php
sed -i "s/\$$user = \$$this->user->login(\$$account, \$$user->password);/\$$user = \$$this->user->login(\$$user);\n\$$url .= \$$this->config->requestType == 'GET' ? '\&' : '?';\n\$$url .= \"{\$$this->config->sessionVar}={\$$this->app->sessionID}\";\n/" zentaoxx/extension/xuan/im/control.php
sed -i "s/\$$file->fullURL/\$$file->webPath/" zentaoxx/extension/xuan/im/control.php
sed -i 's/XXBVERSION/$(XVERSION)/g' zentaoxx/config/ext/_0_xuanxuan.php
diff --git a/VERSION b/VERSION
index eb97ac6557..9d61a5299a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-18.0.beta1
+18.0.beta2
diff --git a/api/v1/entries/task.php b/api/v1/entries/task.php
index d31e606d72..444c47742b 100644
--- a/api/v1/entries/task.php
+++ b/api/v1/entries/task.php
@@ -87,7 +87,7 @@ class taskEntry extends entry
$task->preAndNext['pre'] = $preAndNext->pre ? $preAndNext->pre->id : '';
$task->preAndNext['next'] = $preAndNext->next ? $preAndNext->next->id : '';
- $execution = $this->loadModel('project')->getByID($task->execution, 'execution');
+ $execution = $this->loadModel('project')->getByID($task->execution, 'execution,sprint');
$task->executionStatus = $execution->status;
return $this->send(200, $this->format($task, 'deadline:date,openedBy:user,openedDate:time,assignedTo:user,assignedDate:time,realStarted:time,finishedBy:user,finishedDate:time,closedBy:user,closedDate:time,canceledBy:user,canceledDate:time,lastEditedBy:user,lastEditedDate:time,deleted:bool,mailto:userList'));
diff --git a/api/v1/entries/tasks.php b/api/v1/entries/tasks.php
index 63ed468957..c29d861022 100644
--- a/api/v1/entries/tasks.php
+++ b/api/v1/entries/tasks.php
@@ -20,7 +20,32 @@ class tasksEntry extends entry
*/
public function get($executionID = 0)
{
- if(!$executionID)
+ /* Get tasks by search, search arguments available: pri, assignedTo, status, id, name. Pager arguments will be utilized as well. */
+ if($this->param('search', 0) == 1) // TODO: document this api.
+ {
+ $this->loadModel('task');
+ $searchParams = array();
+ foreach(array('pri' => 'priList', 'assignedTo' => 'assignedToList', 'status' => 'statusList', 'id' => 'idList', 'name' => 'taskName') as $field => $condName)
+ {
+ if($this->param($field, false))
+ {
+ $searchParams[$condName] = $this->param($field);
+ continue;
+ }
+ if($this->param($condName, false)) $searchParams[$condName] = $this->param($condName);
+ }
+
+ $this->app->loadClass('pager', $static = true);
+ $pager = pager::init($this->param('total', 0), $this->param('limit', 20), $this->param('page', 1));
+ $tasks = $this->task->getListByConds((object)$searchParams, $this->param('order', 'id_desc'), $pager);
+
+ $data = new stdclass();
+ $data->status = 'success';
+ $data->data = new stdclass();
+ $data->data->tasks = array_values($tasks);
+ $data->data->pager = (object)$pager;
+ }
+ elseif(!$executionID)
{
/* Get my tasks defaultly. */
$control = $this->loadController('my', 'task');
diff --git a/config/config.php b/config/config.php
index 683b749f39..9f4aa87bf3 100644
--- a/config/config.php
+++ b/config/config.php
@@ -16,7 +16,7 @@ if(!class_exists('config')){class config{}}
if(!function_exists('getWebRoot')){function getWebRoot(){}}
/* 基本设置。Basic settings. */
-$config->version = '18.0.beta1'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
+$config->version = '18.0.beta2'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
$config->liteVersion = '1.2'; // 迅捷版版本。 The version of Lite.
$config->charset = 'UTF-8'; // ZenTaoPHP的编码。 The encoding of ZenTaoPHP.
$config->cookieLife = time() + 2592000; // Cookie的生存时间。The cookie life time.
diff --git a/config/filter.php b/config/filter.php
index b3073c6041..4c950b84f0 100755
--- a/config/filter.php
+++ b/config/filter.php
@@ -2,7 +2,7 @@
$filter = new stdclass();
$filter->rules = new stdclass();
$filter->rules->md5 = '/^[a-z0-9]{32}$/';
-$filter->rules->base64 = '/^[a-zA-Z0-9\+\/\=]+$/';
+$filter->rules->base64 = '/^[a-zA-Z0-9\+\/\=\.]+$/';
$filter->rules->checked = '/^[0-9,\-]+$/';
$filter->rules->idList = '/^[0-9\|]+$/';
$filter->rules->lang = '/^[a-zA-Z_\-]+$/';
diff --git a/db/standard/zentao18.0.beta2.sql b/db/standard/zentao18.0.beta2.sql
new file mode 100644
index 0000000000..a040ed500a
--- /dev/null
+++ b/db/standard/zentao18.0.beta2.sql
@@ -0,0 +1,4084 @@
+CREATE TABLE `zt_account` (
+ `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `type` varchar(255) NOT NULL,
+ `provider` varchar(255) NOT NULL,
+ `adminURI` varchar(255) NOT NULL,
+ `account` varchar(255) NOT NULL,
+ `password` varchar(255) NOT NULL,
+ `email` varchar(255) NOT NULL,
+ `mobile` varchar(255) NOT NULL,
+ `extra` text NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `name` (`name`),
+ KEY `provider` (`provider`),
+ KEY `status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_acl` (
+ `id` mediumint(9) NOT NULL AUTO_INCREMENT,
+ `account` char(30) NOT NULL,
+ `objectType` char(30) NOT NULL,
+ `objectID` mediumint(9) NOT NULL DEFAULT '0',
+ `type` char(40) NOT NULL DEFAULT 'whitelist',
+ `source` char(30) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_action` (
+ `id` int(9) unsigned NOT NULL AUTO_INCREMENT,
+ `objectType` varchar(30) NOT NULL DEFAULT '',
+ `objectID` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `product` text NOT NULL,
+ `project` mediumint(8) unsigned NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `actor` varchar(100) NOT NULL DEFAULT '',
+ `action` varchar(80) NOT NULL DEFAULT '',
+ `date` datetime NOT NULL,
+ `comment` text NOT NULL,
+ `extra` text,
+ `read` enum('0','1') NOT NULL DEFAULT '0',
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `efforted` tinyint(1) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `date` (`date`),
+ KEY `actor` (`actor`),
+ KEY `project` (`project`),
+ KEY `action` (`action`),
+ KEY `objectID` (`objectID`)
+) ENGINE=InnoDB AUTO_INCREMENT=71 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_activity` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `process` mediumint(9) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `optional` varchar(255) NOT NULL,
+ `tailorNorm` varchar(255) NOT NULL,
+ `content` mediumtext NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `assignedBy` varchar(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `order` mediumint(8) DEFAULT '0',
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=90 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_api` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `product` varchar(255) NOT NULL DEFAULT '',
+ `lib` int(10) unsigned NOT NULL DEFAULT '0',
+ `module` int(10) unsigned NOT NULL DEFAULT '0',
+ `title` varchar(100) NOT NULL DEFAULT '',
+ `path` varchar(255) NOT NULL DEFAULT '',
+ `protocol` varchar(10) NOT NULL DEFAULT '',
+ `method` varchar(10) NOT NULL DEFAULT '',
+ `requestType` varchar(100) NOT NULL DEFAULT '',
+ `responseType` varchar(100) NOT NULL DEFAULT '',
+ `status` varchar(20) NOT NULL DEFAULT '',
+ `owner` varchar(30) NOT NULL DEFAULT '0',
+ `desc` mediumtext NULL,
+ `version` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `params` text,
+ `paramsExample` text,
+ `responseExample` text,
+ `response` text,
+ `commonParams` text,
+ `addedBy` varchar(30) NOT NULL DEFAULT '0',
+ `addedDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL DEFAULT '0',
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=70 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_api_lib_release` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `lib` int(10) unsigned NOT NULL DEFAULT '0',
+ `desc` varchar(255) NOT NULL DEFAULT '',
+ `version` varchar(255) NOT NULL DEFAULT '',
+ `snap` mediumtext NOT NULL,
+ `addedBy` varchar(30) NOT NULL DEFAULT '0',
+ `addedDate` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_apispec` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `doc` int(10) unsigned NOT NULL DEFAULT '0',
+ `module` int(10) unsigned NOT NULL DEFAULT '0',
+ `title` varchar(100) NOT NULL DEFAULT '',
+ `path` varchar(255) NOT NULL DEFAULT '',
+ `protocol` varchar(10) NOT NULL DEFAULT '',
+ `method` varchar(10) NOT NULL DEFAULT '',
+ `requestType` varchar(100) NOT NULL DEFAULT '',
+ `responseType` varchar(100) NOT NULL DEFAULT '',
+ `status` varchar(20) NOT NULL DEFAULT '',
+ `owner` varchar(255) NOT NULL DEFAULT '0',
+ `desc` mediumtext NULL,
+ `version` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `params` text,
+ `paramsExample` text,
+ `responseExample` text,
+ `response` text,
+ `addedBy` varchar(30) NOT NULL DEFAULT '0',
+ `addedDate` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=176 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_apistruct` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `lib` int(10) unsigned NOT NULL DEFAULT '0',
+ `name` varchar(30) NOT NULL DEFAULT '',
+ `type` varchar(50) NOT NULL DEFAULT '',
+ `desc` mediumtext NOT NULL DEFAULT '',
+ `version` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `attribute` text,
+ `addedBy` varchar(30) NOT NULL DEFAULT '0',
+ `addedDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL DEFAULT '0',
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_apistruct_spec` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL DEFAULT '',
+ `type` varchar(50) NOT NULL DEFAULT '',
+ `desc` varchar(255) NOT NULL DEFAULT '',
+ `attribute` text,
+ `version` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `addedBy` varchar(30) NOT NULL DEFAULT '0',
+ `addedDate` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_approval` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `flow` mediumint(8) NOT NULL,
+ `objectType` varchar(30) NOT NULL,
+ `objectID` mediumint(9) NOT NULL,
+ `nodes` mediumtext NOT NULL,
+ `version` mediumint(9) NOT NULL,
+ `status` varchar(20) NOT NULL DEFAULT 'doing',
+ `result` varchar(20) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `deleted` tinyint(4) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_approvalflow` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `code` varchar(100) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `version` mediumint(8) NOT NULL DEFAULT '1',
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `type` varchar(30) NOT NULL,
+ `deleted` tinyint(4) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_approvalflowobject` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `root` int(8) NOT NULL,
+ `flow` int(8) NOT NULL,
+ `objectType` char(30) NOT NULL,
+ `objectID` mediumint(9) NOT NULL,
+ `extra` varchar(255) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_approvalflowspec` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `flow` mediumint(8) NOT NULL,
+ `version` mediumint(8) NOT NULL,
+ `nodes` mediumtext NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_approvalnode` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `approval` mediumint(8) NOT NULL,
+ `type` enum('review','cc') NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `account` char(30) NOT NULL,
+ `node` varchar(100) NOT NULL,
+ `reviewType` varchar(100) NOT NULL DEFAULT 'manual',
+ `multipleType` enum('and','or') NOT NULL DEFAULT 'and',
+ `prev` mediumtext NOT NULL,
+ `next` mediumtext NOT NULL,
+ `status` varchar(20) NOT NULL DEFAULT 'wait',
+ `result` varchar(10) NOT NULL,
+ `date` date NOT NULL,
+ `opinion` mediumtext NOT NULL,
+ `extra` mediumtext NOT NULL,
+ `reviewedBy` char(30) NOT NULL,
+ `reviewedDate` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_reviewed_date` (`reviewedDate`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_approvalobject` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `approval` int(8) NOT NULL,
+ `objectType` char(30) NOT NULL,
+ `objectID` mediumint(8) NOT NULL,
+ `extra` varchar(255) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_approvalrole` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `code` char(30) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `desc` text NOT NULL,
+ `users` longtext NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_asset` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `type` varchar(30) NOT NULL,
+ `group` varchar(128) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` char(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_assetlib` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `type` varchar(255) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `order` SMALLINT UNSIGNED NOT NULL DEFAULT '0',
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_attend` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `account` char(30) NOT NULL,
+ `date` date NOT NULL,
+ `signIn` time NOT NULL,
+ `signOut` time NOT NULL,
+ `status` varchar(30) NOT NULL DEFAULT '',
+ `ip` varchar(15) NOT NULL,
+ `device` varchar(30) NOT NULL,
+ `client` varchar(20) NOT NULL,
+ `manualIn` time NOT NULL,
+ `manualOut` time NOT NULL,
+ `reason` varchar(30) NOT NULL DEFAULT '',
+ `desc` text NOT NULL,
+ `reviewStatus` varchar(30) NOT NULL DEFAULT '',
+ `reviewedBy` char(30) NOT NULL,
+ `reviewedDate` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `attend` (`date`,`account`),
+ KEY `account` (`account`),
+ KEY `date` (`date`),
+ KEY `status` (`status`),
+ KEY `reason` (`reason`),
+ KEY `reviewStatus` (`reviewStatus`),
+ KEY `reviewedBy` (`reviewedBy`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_attendstat` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `account` char(30) NOT NULL,
+ `month` char(10) NOT NULL DEFAULT '',
+ `normal` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `late` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `early` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `absent` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `trip` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `egress` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `lieu` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `paidLeave` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `unpaidLeave` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `timeOvertime` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `restOvertime` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `holidayOvertime` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `deserve` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `actual` decimal(12,2) NOT NULL DEFAULT '0.00',
+ `status` char(30) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `attend` (`month`,`account`),
+ KEY `account` (`account`),
+ KEY `month` (`month`),
+ KEY `status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_auditcl` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `model` char(30) NOT NULL DEFAULT 'waterfall',
+ `practiceArea` char(30) NOT NULL,
+ `type` char(30) NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `objectType` char(30) NOT NULL,
+ `objectID` int(10) DEFAULT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `assignedBy` varchar(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_auditplan` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `dateType` char(30) DEFAULT NULL,
+ `config` text,
+ `objectID` mediumint(9) NOT NULL,
+ `objectType` char(30) NOT NULL,
+ `process` mediumint(9) NOT NULL,
+ `processType` char(30) NOT NULL,
+ `checkDate` date NOT NULL,
+ `checkedBy` varchar(30) NOT NULL,
+ `realCheckDate` date NOT NULL,
+ `result` char(30) NOT NULL,
+ `project` mediumint(8) unsigned NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `assignedBy` varchar(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ `checkBy` varchar(30) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_auditresult` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `auditplan` mediumint(8) NOT NULL,
+ `listID` mediumint(8) NOT NULL,
+ `result` char(30) NOT NULL,
+ `checkedBy` varchar(30) NOT NULL,
+ `checkedDate` date NOT NULL,
+ `comment` text NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `assignedBy` varchar(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_baseimage` (
+ `id` smallint(7) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL DEFAULT '',
+ `path` varchar(255) NOT NULL DEFAULT '',
+ `osType` varchar(50) NOT NULL DEFAULT '',
+ `os` varchar(50) NOT NULL DEFAULT '',
+ `osCategory` varchar(50) NOT NULL DEFAULT '',
+ `osArch` varchar(50) NOT NULL DEFAULT '',
+ `osLang` varchar(50) NOT NULL DEFAULT '',
+ `suggestCore` tinyint(1) unsigned NOT NULL DEFAULT '0',
+ `suggestMemory` mediumint(6) unsigned NOT NULL DEFAULT '0',
+ `suggestVolume` mediumint(6) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_baseimagebrowser` (
+ `vmBackingID` int(10) NOT NULL,
+ `browserID` int(10) NOT NULL,
+ PRIMARY KEY (`vmBackingID`,`browserID`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_basicmeas` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `purpose` varchar(50) NOT NULL,
+ `scope` char(30) NOT NULL,
+ `object` char(30) NOT NULL,
+ `name` varchar(90) NOT NULL,
+ `code` char(30) NOT NULL,
+ `unit` varchar(10) NOT NULL,
+ `configure` text,
+ `params` text,
+ `definition` text,
+ `source` varchar(255) DEFAULT NULL,
+ `collectType` varchar(30) NOT NULL,
+ `collectConf` text NOT NULL,
+ `execTime` varchar(30) NOT NULL,
+ `collectedBy` varchar(10) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `order` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=54 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_block` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `account` char(30) NOT NULL,
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `module` varchar(20) NOT NULL,
+ `type` char(30) NOT NULL,
+ `title` varchar(100) NOT NULL,
+ `source` varchar(20) NOT NULL,
+ `block` varchar(30) NOT NULL,
+ `params` text NOT NULL,
+ `order` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `grid` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `height` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `hidden` tinyint(1) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `account_vision_module_type_order` (`account`,`vision`,`module`,`type`,`order`),
+ KEY `account` (`account`)
+) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_branch` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `product` mediumint(8) unsigned NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `default` enum('0','1') NOT NULL DEFAULT '0',
+ `status` enum('active','closed') NOT NULL DEFAULT 'active',
+ `desc` varchar(255) NOT NULL,
+ `createdDate` date NOT NULL,
+ `closedDate` date NOT NULL,
+ `order` smallint(5) unsigned NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `product` (`product`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_browser` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL DEFAULT '',
+ `type` varchar(255) NOT NULL DEFAULT '',
+ `version` varchar(255) NOT NULL DEFAULT '',
+ `lang` varchar(255) NOT NULL DEFAULT '',
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_budget` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `stage` char(30) NOT NULL,
+ `subject` mediumint(8) NOT NULL,
+ `amount` char(30) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` date NOT NULL,
+ `lastEditedBy` char(30) NOT NULL,
+ `lastEditedDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_bug` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `injection` mediumint(8) unsigned NOT NULL,
+ `identify` mediumint(8) unsigned NOT NULL,
+ `branch` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `module` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `execution` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `plan` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `story` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `storyVersion` smallint(6) NOT NULL DEFAULT '1',
+ `task` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `toTask` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `toStory` mediumint(8) NOT NULL DEFAULT '0',
+ `title` varchar(255) NOT NULL,
+ `keywords` varchar(255) NOT NULL,
+ `severity` tinyint(4) NOT NULL DEFAULT '0',
+ `pri` tinyint(3) unsigned NOT NULL,
+ `type` varchar(30) NOT NULL DEFAULT '',
+ `os` varchar(255) NOT NULL DEFAULT '',
+ `browser` varchar(255) NOT NULL DEFAULT '',
+ `hardware` varchar(30) NOT NULL,
+ `found` varchar(30) NOT NULL DEFAULT '',
+ `steps` mediumtext NOT NULL,
+ `status` enum('active','resolved','closed') NOT NULL DEFAULT 'active',
+ `subStatus` varchar(30) NOT NULL DEFAULT '',
+ `color` char(7) NOT NULL,
+ `confirmed` tinyint(1) NOT NULL DEFAULT '0',
+ `activatedCount` smallint(6) NOT NULL,
+ `activatedDate` datetime NOT NULL,
+ `feedbackBy` varchar(100) NOT NULL,
+ `notifyEmail` varchar(100) NOT NULL,
+ `mailto` text,
+ `openedBy` varchar(30) NOT NULL DEFAULT '',
+ `openedDate` datetime NOT NULL,
+ `openedBuild` varchar(255) NOT NULL,
+ `assignedTo` varchar(30) NOT NULL DEFAULT '',
+ `assignedDate` datetime NOT NULL,
+ `deadline` date NOT NULL,
+ `resolvedBy` varchar(30) NOT NULL DEFAULT '',
+ `resolution` varchar(30) NOT NULL DEFAULT '',
+ `resolvedBuild` varchar(30) NOT NULL DEFAULT '',
+ `resolvedDate` datetime NOT NULL,
+ `closedBy` varchar(30) NOT NULL DEFAULT '',
+ `closedDate` datetime NOT NULL,
+ `duplicateBug` mediumint(8) unsigned NOT NULL,
+ `linkBug` varchar(255) NOT NULL,
+ `case` mediumint(8) unsigned NOT NULL,
+ `caseVersion` smallint(6) NOT NULL DEFAULT '1',
+ `feedback` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `result` mediumint(8) unsigned NOT NULL,
+ `repo` mediumint(8) unsigned NOT NULL,
+ `mr` mediumint(8) unsigned NOT NULL,
+ `entry` text NOT NULL,
+ `lines` varchar(10) NOT NULL,
+ `v1` varchar(40) NOT NULL,
+ `v2` varchar(40) NOT NULL,
+ `repoType` varchar(30) NOT NULL DEFAULT '',
+ `issueKey` varchar(50) NOT NULL DEFAULT '',
+ `testtask` mediumint(8) unsigned NOT NULL,
+ `lastEditedBy` varchar(30) NOT NULL DEFAULT '',
+ `lastEditedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `product` (`product`),
+ KEY `execution` (`execution`),
+ KEY `status` (`status`),
+ KEY `plan` (`plan`),
+ KEY `story` (`story`),
+ KEY `case` (`case`),
+ KEY `toStory` (`toStory`),
+ KEY `result` (`result`),
+ KEY `assignedTo` (`assignedTo`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_build` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `branch` varchar(255) NOT NULL DEFAULT '0',
+ `execution` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `builds` varchar(255) NOT NULL,
+ `name` char(150) NOT NULL,
+ `scmPath` char(255) NOT NULL,
+ `filePath` char(255) NOT NULL,
+ `date` date NOT NULL,
+ `stories` text NOT NULL,
+ `bugs` text NOT NULL,
+ `builder` char(30) NOT NULL DEFAULT '',
+ `desc` mediumtext NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `product` (`product`),
+ KEY `execution` (`execution`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_burn` (
+ `execution` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL,
+ `task` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `date` date NOT NULL,
+ `estimate` float NOT NULL,
+ `left` float NOT NULL,
+ `consumed` float NOT NULL,
+ `storyPoint` float NOT NULL,
+ PRIMARY KEY (`execution`,`date`,`task`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_case` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `execution` mediumint(8) unsigned NOT NULL,
+ `branch` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `lib` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `module` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `path` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `story` mediumint(30) unsigned NOT NULL DEFAULT '0',
+ `storyVersion` smallint(6) NOT NULL DEFAULT '1',
+ `title` varchar(255) NOT NULL,
+ `precondition` text NOT NULL,
+ `keywords` varchar(255) NOT NULL,
+ `pri` tinyint(3) unsigned NOT NULL DEFAULT '3',
+ `type` char(30) NOT NULL DEFAULT '1',
+ `auto` varchar(10) NOT NULL DEFAULT 'no',
+ `frame` varchar(10) NOT NULL,
+ `stage` varchar(255) NOT NULL,
+ `howRun` varchar(30) NOT NULL,
+ `scriptedBy` varchar(30) NOT NULL,
+ `scriptedDate` date NOT NULL,
+ `scriptStatus` varchar(30) NOT NULL,
+ `scriptLocation` varchar(255) NOT NULL,
+ `status` char(30) NOT NULL DEFAULT '1',
+ `subStatus` varchar(30) NOT NULL DEFAULT '',
+ `color` char(7) NOT NULL,
+ `frequency` enum('1','2','3') NOT NULL DEFAULT '1',
+ `order` tinyint(30) unsigned NOT NULL DEFAULT '0',
+ `openedBy` char(30) NOT NULL DEFAULT '',
+ `openedDate` datetime NOT NULL,
+ `reviewedBy` varchar(255) NOT NULL,
+ `reviewedDate` date NOT NULL,
+ `lastEditedBy` char(30) NOT NULL DEFAULT '',
+ `lastEditedDate` datetime NOT NULL,
+ `version` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `linkCase` varchar(255) NOT NULL,
+ `fromBug` mediumint(8) unsigned NOT NULL,
+ `fromCaseID` mediumint(8) unsigned NOT NULL,
+ `fromCaseVersion` mediumint(8) unsigned NOT NULL DEFAULT '1',
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ `lastRunner` varchar(30) NOT NULL,
+ `lastRunDate` datetime NOT NULL,
+ `lastRunResult` char(30) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `product` (`product`),
+ KEY `story` (`story`),
+ KEY `fromBug` (`fromBug`),
+ KEY `module` (`module`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_casestep` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `parent` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `case` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `version` smallint(3) unsigned NOT NULL DEFAULT '0',
+ `type` varchar(10) NOT NULL DEFAULT 'step',
+ `desc` text NOT NULL,
+ `expect` text NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `case` (`case`),
+ KEY `version` (`version`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_cfd` (
+ `id` int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ `execution` int(8) NOT NULL,
+ `type` char(30) NOT NULL,
+ `name` char(30) NOT NULL,
+ `count` smallint NOT NULL,
+ `date` date NOT NULL,
+ UNIQUE KEY `execution_type_name_date` (`execution`,`type`,`name`,`date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_cmcl` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `type` char(30) NOT NULL,
+ `title` int(11) NOT NULL,
+ `contents` text NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `order` int(11) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_company` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` char(120) DEFAULT NULL,
+ `phone` char(20) DEFAULT NULL,
+ `fax` char(20) DEFAULT NULL,
+ `address` char(120) DEFAULT NULL,
+ `zipcode` char(10) DEFAULT NULL,
+ `website` char(120) DEFAULT NULL,
+ `backyard` char(120) DEFAULT NULL,
+ `guest` enum('1','0') NOT NULL DEFAULT '0',
+ `admins` char(255) DEFAULT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_compile` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(50) NOT NULL,
+ `job` mediumint(8) unsigned NOT NULL,
+ `queue` mediumint(8) NOT NULL,
+ `status` varchar(255) NOT NULL,
+ `logs` text,
+ `atTime` varchar(10) NOT NULL,
+ `testtask` mediumint(8) unsigned NOT NULL,
+ `tag` varchar(255) NOT NULL,
+ `times` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `updateDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_config` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `owner` char(30) NOT NULL DEFAULT '',
+ `module` varchar(30) NOT NULL,
+ `section` char(30) NOT NULL DEFAULT '',
+ `key` char(30) NOT NULL DEFAULT '',
+ `value` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `unique` (`vision`,`owner`,`module`,`section`,`key`)
+) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_cron` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `m` varchar(20) NOT NULL,
+ `h` varchar(20) NOT NULL,
+ `dom` varchar(20) NOT NULL,
+ `mon` varchar(20) NOT NULL,
+ `dow` varchar(20) NOT NULL,
+ `command` text NOT NULL,
+ `remark` varchar(255) NOT NULL,
+ `type` varchar(20) NOT NULL,
+ `buildin` tinyint(1) NOT NULL DEFAULT '0',
+ `status` varchar(20) NOT NULL,
+ `lastTime` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `lastTime` (`lastTime`)
+) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_deploy` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `begin` datetime NOT NULL,
+ `end` datetime NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `status` varchar(20) NOT NULL,
+ `owner` char(30) NOT NULL,
+ `members` text NOT NULL,
+ `notify` text NOT NULL,
+ `cases` text NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `result` varchar(20) NOT NULL,
+ `deleted` enum('0','1') NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_chart` (
+ `id` mediumint NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `type` varchar(30) NOT NULL,
+ `dataset` varchar(30) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `settings` mediumtext NOT NULL,
+ `filters` mediumtext NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `deleted` tinyint NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_dashboard` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `module` mediumint NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `layout` mediumtext NOT NULL,
+ `filters` mediumtext NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `deleted` tinyint NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_dataset` (
+ `id` mediumint unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(155) NOT NULL,
+ `sql` text NOT NULL,
+ `fields` mediumtext NOT NULL,
+ `objects` mediumtext NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `deleted` tinyint NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_deployproduct` (
+ `deploy` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL,
+ `release` mediumint(8) unsigned NOT NULL,
+ `package` varchar(255) NOT NULL,
+ UNIQUE KEY `deploy_product_release` (`deploy`,`product`,`release`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_deployscope` (
+ `deploy` mediumint(8) unsigned NOT NULL,
+ `service` mediumint(8) unsigned NOT NULL,
+ `hosts` text NOT NULL,
+ `remove` text NOT NULL,
+ `add` text NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_deploystep` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `deploy` mediumint(8) unsigned NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `begin` datetime NOT NULL,
+ `end` datetime NOT NULL,
+ `stage` varchar(30) NOT NULL,
+ `content` text NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `assignedTo` char(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `finishedBy` char(30) NOT NULL,
+ `finishedDate` datetime NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_dept` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` char(60) NOT NULL,
+ `parent` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `path` char(255) NOT NULL DEFAULT '',
+ `grade` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `order` smallint(4) unsigned NOT NULL DEFAULT '0',
+ `position` char(30) NOT NULL DEFAULT '',
+ `function` char(255) NOT NULL DEFAULT '',
+ `manager` char(30) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`),
+ KEY `parent` (`parent`),
+ KEY `path` (`path`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_design` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` varchar(255) NOT NULL,
+ `product` varchar(255) NOT NULL,
+ `commit` text NOT NULL,
+ `commitedBy` varchar(30) NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `name` varchar(255) NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `assignedBy` varchar(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ `story` char(30) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `version` smallint(6) NOT NULL,
+ `type` char(30) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_designspec` (
+ `design` mediumint(8) NOT NULL,
+ `version` smallint(6) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `files` varchar(255) NOT NULL,
+ UNIQUE KEY `design` (`design`,`version`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_doc` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `project` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `lib` varchar(30) NOT NULL,
+ `template` varchar(30) NOT NULL,
+ `templateType` varchar(30) NOT NULL,
+ `chapterType` varchar(30) NOT NULL,
+ `module` varchar(30) NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `keywords` varchar(255) NOT NULL,
+ `type` varchar(30) NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `parent` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `path` char(255) NOT NULL DEFAULT '',
+ `grade` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `order` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `views` smallint(5) unsigned NOT NULL,
+ `assetLib` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `assetLibType` varchar(30) NOT NULL DEFAULT '',
+ `from` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `fromVersion` smallint(6) NOT NULL DEFAULT '1',
+ `draft` longtext NOT NULL,
+ `collector` text NOT NULL,
+ `addedBy` varchar(30) NOT NULL,
+ `addedDate` datetime NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `approvedDate` date NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `mailto` text,
+ `acl` varchar(10) NOT NULL DEFAULT 'open',
+ `groups` varchar(255) NOT NULL,
+ `users` text NOT NULL,
+ `version` smallint(5) unsigned NOT NULL DEFAULT '1',
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `product` (`product`),
+ KEY `execution` (`execution`),
+ KEY `lib` (`lib`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_doccontent` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `doc` mediumint(8) unsigned NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `digest` varchar(255) NOT NULL,
+ `content` longtext NOT NULL,
+ `files` text NOT NULL,
+ `type` varchar(10) NOT NULL,
+ `version` smallint(5) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `doc_version` (`doc`,`version`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_doclib` (
+ `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
+ `type` varchar(30) NOT NULL,
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `product` mediumint(8) unsigned NOT NULL,
+ `project` mediumint(8) unsigned NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `name` varchar(60) NOT NULL,
+ `baseUrl` varchar(255) NOT NULL DEFAULT '',
+ `acl` varchar(10) NOT NULL DEFAULT 'open',
+ `groups` varchar(255) NOT NULL,
+ `users` text NOT NULL,
+ `main` enum('0','1') NOT NULL DEFAULT '0',
+ `collector` text NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `order` tinyint(5) unsigned NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `product` (`product`),
+ KEY `execution` (`execution`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_domain` (
+ `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
+ `domain` varchar(255) NOT NULL,
+ `adminURI` varchar(255) NOT NULL,
+ `resolverURI` varchar(255) NOT NULL,
+ `register` varchar(255) NOT NULL,
+ `expiredDate` datetime NOT NULL,
+ `renew` varchar(255) NOT NULL,
+ `account` varchar(255) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `domain` (`domain`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_durationestimation` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `stage` mediumint(9) NOT NULL,
+ `workload` varchar(255) NOT NULL,
+ `worktimeRate` varchar(255) NOT NULL,
+ `people` varchar(255) NOT NULL,
+ `startDate` date NOT NULL,
+ `endDate` date NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_effort` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `objectType` varchar(30) NOT NULL,
+ `objectID` mediumint(8) unsigned NOT NULL,
+ `product` text NOT NULL,
+ `project` mediumint(8) unsigned NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `account` varchar(30) NOT NULL,
+ `work` text,
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `date` date NOT NULL,
+ `left` float NOT NULL,
+ `consumed` float NOT NULL,
+ `begin` smallint(4) unsigned zerofill NOT NULL,
+ `end` smallint(4) unsigned zerofill NOT NULL,
+ `extra` text NOT NULL,
+ `order` tinyint unsigned NOT NULL DEFAULT '0',
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `execution` (`execution`),
+ KEY `objectID` (`objectID`),
+ KEY `date` (`date`),
+ KEY `account` (`account`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_entry` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(50) NOT NULL,
+ `account` varchar(30) NOT NULL DEFAULT '',
+ `code` varchar(20) NOT NULL,
+ `key` varchar(32) NOT NULL,
+ `freePasswd` enum('0','1') NOT NULL DEFAULT '0',
+ `ip` varchar(100) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `calledTime` int(10) unsigned NOT NULL DEFAULT '0',
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_expect` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `userID` mediumint(8) NOT NULL,
+ `project` mediumint(8) NOT NULL DEFAULT '0',
+ `expect` text NOT NULL,
+ `progress` text NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_extension` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(150) NOT NULL,
+ `code` varchar(30) NOT NULL,
+ `version` varchar(50) NOT NULL,
+ `author` varchar(100) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `license` text NOT NULL,
+ `type` varchar(20) NOT NULL DEFAULT 'extension',
+ `site` varchar(150) NOT NULL,
+ `zentaoCompatible` varchar(100) NOT NULL,
+ `installedTime` datetime NOT NULL,
+ `depends` varchar(100) NOT NULL,
+ `dirs` mediumtext NOT NULL,
+ `files` mediumtext NOT NULL,
+ `status` varchar(20) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `code` (`code`),
+ KEY `name` (`name`),
+ KEY `installedTime` (`installedTime`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_faq` (
+ `id` mediumint(9) NOT NULL AUTO_INCREMENT,
+ `module` mediumint(9) NOT NULL,
+ `product` mediumint(9) NOT NULL,
+ `question` varchar(255) NOT NULL,
+ `answer` text NOT NULL,
+ `addedtime` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_feedback` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `product` mediumint(8) unsigned NOT NULL,
+ `module` mediumint(8) unsigned NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `type` char(30) NOT NULL,
+ `solution` char(30) NOT NULL,
+ `desc` text NOT NULL,
+ `pri` tinyint unsigned NOT NULL DEFAULT 2,
+ `status` varchar(30) NOT NULL,
+ `subStatus` varchar(30) NOT NULL DEFAULT '',
+ `public` enum('0','1') NOT NULL DEFAULT '0',
+ `notify` enum('0','1') NOT NULL DEFAULT '0',
+ `notifyEmail` varchar(100) NOT NULL,
+ `source` varchar(255) NOT NULL,
+ `likes` text NOT NULL,
+ `result` mediumint(8) unsigned NOT NULL,
+ `faq` mediumint(8) unsigned NOT NULL,
+ `openedBy` char(30) NOT NULL,
+ `openedDate` datetime NOT NULL,
+ `reviewedBy` varchar(255) NOT NULL,
+ `reviewedDate` datetime NOT NULL,
+ `processedBy` char(30) NOT NULL,
+ `processedDate` datetime NOT NULL,
+ `closedBy` char(30) NOT NULL,
+ `closedDate` datetime NOT NULL,
+ `closedReason` varchar(30) NOT NULL,
+ `editedBy` char(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `assignedTo` varchar(255) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `activatedBy` varchar(30) NOT NULL,
+ `activatedDate` datetime NOT NULL,
+ `feedbackBy` varchar(100) NOT NULL,
+ `repeatFeedback` mediumint(8) NOT NULL DEFAULT 0,
+ `mailto` varchar(255) NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_feedbackview` (
+ `account` char(30) NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL,
+ UNIQUE KEY `account_product` (`account`,`product`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_file` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `pathname` char(100) NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `extension` char(30) NOT NULL,
+ `size` int(10) unsigned NOT NULL DEFAULT '0',
+ `objectType` char(30) NOT NULL,
+ `objectID` mediumint(9) NOT NULL,
+ `addedBy` char(30) NOT NULL DEFAULT '',
+ `addedDate` datetime NOT NULL,
+ `downloads` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `extra` varchar(255) NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `objectType` (`objectType`),
+ KEY `objectID` (`objectID`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_gapanalysis` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `account` varchar(30) NOT NULL,
+ `role` varchar(20) NOT NULL,
+ `analysis` mediumtext NOT NULL,
+ `needTrain` enum('no','yes') NOT NULL DEFAULT 'no',
+ `createdBy` char(30) DEFAULT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `project_account` (`project`,`account`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_group` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `name` char(30) NOT NULL,
+ `role` char(30) NOT NULL DEFAULT '',
+ `desc` char(255) NOT NULL DEFAULT '',
+ `acl` text,
+ `developer` enum('0','1') NOT NULL DEFAULT '1',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_grouppriv` (
+ `group` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `module` char(30) NOT NULL DEFAULT '',
+ `method` char(30) NOT NULL DEFAULT '',
+ UNIQUE KEY `group` (`group`,`module`,`method`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_history` (
+ `id` int(9) unsigned NOT NULL AUTO_INCREMENT,
+ `action` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `field` varchar(30) NOT NULL DEFAULT '',
+ `old` text NOT NULL,
+ `new` text NOT NULL,
+ `diff` mediumtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `action` (`action`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_holiday` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(30) NOT NULL DEFAULT '',
+ `type` enum('holiday','working') NOT NULL DEFAULT 'holiday',
+ `desc` mediumtext NOT NULL,
+ `year` char(4) NOT NULL,
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `year` (`year`),
+ KEY `name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_host` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `assetID` mediumint(8) unsigned NOT NULL,
+ `admin` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `serverRoom` mediumint(8) unsigned NOT NULL,
+ `cabinet` varchar(128) NOT NULL,
+ `serverModel` varchar(256) NOT NULL,
+ `hardwareType` varchar(64) NOT NULL,
+ `hostType` enum('physical','virtual') NOT NULL,
+ `cpuBrand` varchar(128) NOT NULL,
+ `cpuModel` varchar(128) NOT NULL,
+ `cpuNumber` varchar(16) NOT NULL,
+ `cpuCores` varchar(30) NOT NULL,
+ `cpuRate` varchar(30) NOT NULL,
+ `memory` varchar(30) NOT NULL,
+ `diskType` varchar(30) NOT NULL,
+ `diskSize` varchar(30) NOT NULL,
+ `unit` enum('GB','TB') NOT NULL DEFAULT 'GB',
+ `privateIP` varchar(128) NOT NULL,
+ `publicIP` varchar(128) NOT NULL,
+ `nic` varchar(128) NOT NULL,
+ `mac` varchar(128) NOT NULL,
+ `osName` varchar(64) NOT NULL,
+ `osVersion` varchar(64) NOT NULL,
+ `webserver` varchar(128) NOT NULL,
+ `database` varchar(128) NOT NULL,
+ `language` varchar(16) NOT NULL,
+ `status` varchar(50) NOT NULL,
+ `agentPort` varchar(10) NOT NULL,
+ `instanceNum` tinyint(4) NOT NULL DEFAULT '0',
+ `pri` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `heartbeatTime` datetime NOT NULL,
+ `tags` varchar(50) NOT NULL DEFAULT '',
+ `provider` varchar(255) NOT NULL DEFAULT '',
+ `bridgeID` varchar(255) NOT NULL DEFAULT '',
+ `cloudKey` varchar(255) NOT NULL DEFAULT '',
+ `cloudSecret` varchar(255) NOT NULL DEFAULT '',
+ `cloudRegion` varchar(255) NOT NULL DEFAULT '',
+ `cloudNamespace` varchar(255) NOT NULL DEFAULT '',
+ `cloudUser` varchar(255) NOT NULL DEFAULT '',
+ `cloudAccount` varchar(255) NOT NULL DEFAULT '',
+ `cloudPassword` varchar(255) NOT NULL DEFAULT '',
+ `couldVPC` varchar(255) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_im_chat` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `gid` char(40) NOT NULL DEFAULT '',
+ `name` varchar(60) NOT NULL DEFAULT '',
+ `type` varchar(20) NOT NULL DEFAULT 'group',
+ `admins` varchar(255) NOT NULL DEFAULT '',
+ `committers` varchar(255) NOT NULL DEFAULT '',
+ `subject` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `public` enum('0','1') NOT NULL DEFAULT '0',
+ `createdBy` varchar(30) NOT NULL DEFAULT '',
+ `createdDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `ownedBy` varchar(30) NOT NULL DEFAULT '',
+ `editedBy` varchar(30) NOT NULL DEFAULT '',
+ `editedDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `mergedDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `lastActiveTime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `lastMessage` int(11) unsigned NOT NULL DEFAULT '0',
+ `lastMessageIndex` int(11) unsigned NOT NULL DEFAULT 0,
+ `dismissDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `pinnedMessages` text NOT NULL,
+ `mergedChats` text NOT NULL DEFAULT '',
+ `adminInvite` enum('0','1') NOT NULL DEFAULT '0',
+ `avatar` text NOT NULL DEFAULT '',
+ `archiveDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ PRIMARY KEY (`id`),
+ KEY `gid` (`gid`),
+ KEY `name` (`name`),
+ KEY `type` (`type`),
+ KEY `public` (`public`),
+ KEY `createdBy` (`createdBy`),
+ KEY `editedBy` (`editedBy`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_im_chat_message_index` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `gid` char(40) NOT NULL,
+ `tableName` char(64) NOT NULL,
+ `start` int(11) unsigned NOT NULL,
+ `end` int(11) unsigned NOT NULL,
+ `startIndex` int(11) unsigned NOT NULL,
+ `endIndex` int(11) unsigned NOT NULL,
+ `startDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `endDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `count` mediumint(8) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `chattable` (`gid`,`tableName`),
+ KEY `start` (`start`),
+ KEY `end` (`end`),
+ KEY `startDate` (`startDate`),
+ KEY `endDate` (`endDate`),
+ KEY `chatstartindex` (`gid`,`startIndex`),
+ KEY `chatendindex` (`gid`,`endIndex`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_im_chatuser` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `cgid` char(40) NOT NULL DEFAULT '',
+ `user` mediumint(8) NOT NULL DEFAULT '0',
+ `order` smallint(5) NOT NULL DEFAULT '0',
+ `star` enum('0','1') NOT NULL DEFAULT '0',
+ `hide` enum('0','1') NOT NULL DEFAULT '0',
+ `mute` enum('0','1') NOT NULL DEFAULT '0',
+ `freeze` enum('0','1') NOT NULL DEFAULT '0',
+ `join` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `quit` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `category` varchar(40) NOT NULL DEFAULT '',
+ `lastReadMessage` int(11) unsigned NOT NULL DEFAULT '0',
+ `lastReadMessageIndex` int(11) unsigned NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `chatuser` (`cgid`,`user`),
+ KEY `cgid` (`cgid`),
+ KEY `user` (`user`),
+ KEY `order` (`order`),
+ KEY `star` (`star`),
+ KEY `hide` (`hide`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_im_client` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `version` char(30) NOT NULL DEFAULT '',
+ `desc` varchar(100) NOT NULL DEFAULT '',
+ `changeLog` text NOT NULL,
+ `strategy` varchar(10) NOT NULL DEFAULT '',
+ `downloads` text NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `createdBy` varchar(30) NOT NULL DEFAULT '',
+ `editedDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL DEFAULT '',
+ `status` enum('released','wait') NOT NULL DEFAULT 'wait',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_im_conference` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `rid` char(40) NOT NULL DEFAULT '',
+ `cgid` char(40) NOT NULL DEFAULT '',
+ `status` enum('closed','open') NOT NULL DEFAULT 'closed',
+ `participants` text NOT NULL,
+ `invitee` text NOT NULL,
+ `openedBy` mediumint(8) NOT NULL DEFAULT '0',
+ `openedDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_im_conferenceaction` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `rid` char(40) NOT NULL DEFAULT '',
+ `type` enum('create','invite','join','leave','close','publish') NOT NULL DEFAULT 'create',
+ `data` text NOT NULL,
+ `user` mediumint(8) NOT NULL DEFAULT '0',
+ `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `device` char(40) NOT NULL DEFAULT 'default',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_im_message` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `gid` char(40) NOT NULL DEFAULT '',
+ `cgid` char(40) NOT NULL DEFAULT '',
+ `user` varchar(30) NOT NULL DEFAULT '',
+ `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `index` int(11) unsigned NOT NULL DEFAULT 0,
+ `type` enum('normal','broadcast','notify','bulletin','botcommand') NOT NULL DEFAULT 'normal',
+ `content` text NOT NULL,
+ `contentType` enum('text','plain','emotion','image','file','object','code') NOT NULL DEFAULT 'text',
+ `data` text NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `mgid` (`gid`),
+ KEY `mcgid` (`cgid`),
+ KEY `muser` (`user`),
+ KEY `mtype` (`type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_im_message_backup` (
+ `id` int(11) unsigned NOT NULL,
+ `gid` char(40) NOT NULL DEFAULT '',
+ `cgid` char(40) NOT NULL DEFAULT '',
+ `user` varchar(30) NOT NULL DEFAULT '',
+ `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `index` int(11) unsigned NOT NULL DEFAULT 0,
+ `type` enum('normal','broadcast','notify') NOT NULL DEFAULT 'normal',
+ `content` text NOT NULL,
+ `contentType` enum('text','plain','emotion','image','file','object','code') NOT NULL DEFAULT 'text',
+ `data` text NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_im_message_index` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `tableName` char(64) NOT NULL,
+ `start` int(11) unsigned NOT NULL,
+ `end` int(11) unsigned NOT NULL,
+ `startDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `endDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `chats` text NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `tableName` (`tableName`),
+ KEY `start` (`start`),
+ KEY `end` (`end`),
+ KEY `startDate` (`startDate`),
+ KEY `endDate` (`endDate`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_im_messagestatus` (
+ `user` mediumint(8) NOT NULL DEFAULT '0',
+ `message` int(11) unsigned NOT NULL,
+ `status` enum('waiting','sent','readed','deleted') NOT NULL DEFAULT 'waiting',
+ UNIQUE KEY `user` (`user`,`message`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_im_queue` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `type` char(30) NOT NULL,
+ `content` text NOT NULL,
+ `addDate` datetime NOT NULL,
+ `processDate` datetime NOT NULL,
+ `result` text NOT NULL,
+ `status` char(30) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_im_userdevice` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `user` mediumint(8) NOT NULL DEFAULT '0',
+ `device` char(40) NOT NULL DEFAULT 'default',
+ `deviceID` char(40) NOT NULL DEFAULT '',
+ `token` char(64) NOT NULL DEFAULT '',
+ `validUntil` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `lastLogin` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `lastLogout` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `userdevice` (`user`,`device`),
+ KEY `user` (`user`),
+ KEY `lastLogin` (`lastLogin`),
+ KEY `lastLogout` (`lastLogout`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_intervention` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `activity` mediumint(8) NOT NULL,
+ `status` char(30) NOT NULL,
+ `partake` text NOT NULL,
+ `begin` date NOT NULL,
+ `realBegin` date NOT NULL,
+ `situation` varchar(255) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `project` (`project`,`activity`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_issue` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `resolvedBy` varchar(30) NOT NULL,
+ `project` varchar(255) NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `pri` char(30) NOT NULL,
+ `severity` char(30) NOT NULL,
+ `type` char(30) NOT NULL,
+ `activity` varchar(255) NOT NULL,
+ `deadline` date NOT NULL,
+ `resolution` char(30) NOT NULL,
+ `resolutionComment` text NOT NULL,
+ `objectID` varchar(255) NOT NULL,
+ `resolvedDate` date NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `owner` varchar(255) NOT NULL,
+ `lib` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `from` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `version` smallint(6) NOT NULL DEFAULT '1',
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `activateBy` varchar(30) NOT NULL,
+ `activateDate` date NOT NULL,
+ `closedBy` varchar(30) NOT NULL,
+ `closedDate` date NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `assignedBy` varchar(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `approvedDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_job` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(50) NOT NULL,
+ `repo` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL,
+ `frame` varchar(20) NOT NULL,
+ `engine` varchar(20) NOT NULL,
+ `server` mediumint(8) unsigned NOT NULL,
+ `pipeline` varchar(500) NOT NULL,
+ `triggerType` varchar(255) NOT NULL,
+ `sonarqubeServer` mediumint(8) unsigned NOT NULL,
+ `projectKey` varchar(255) NOT NULL,
+ `svnDir` varchar(255) NOT NULL,
+ `atDay` varchar(255) DEFAULT NULL,
+ `atTime` varchar(10) DEFAULT NULL,
+ `customParam` text NOT NULL,
+ `comment` varchar(255) DEFAULT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `lastExec` datetime DEFAULT NULL,
+ `lastStatus` varchar(255) DEFAULT NULL,
+ `lastTag` varchar(255) DEFAULT NULL,
+ `lastSyncDate` datetime DEFAULT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_kanban` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `space` mediumint(8) unsigned NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `owner` varchar(30) NOT NULL,
+ `team` text NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `acl` char(30) NOT NULL DEFAULT 'open',
+ `whitelist` text NOT NULL,
+ `archived` enum('0','1') NOT NULL DEFAULT '1',
+ `performable` 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 DEFAULT '0',
+ `showWIP` enum('0','1') NOT NULL DEFAULT '1',
+ `fluidBoard` enum('0','1') NOT NULL DEFAULT '0',
+ `colWidth` smallint(4) NOT NULL DEFAULT '264',
+ `minColWidth` smallint(4) NOT NULL DEFAULT '200',
+ `maxColWidth` smallint(4) NOT NULL DEFAULT '384',
+ `object` varchar(255) NOT NULL,
+ `alignment` varchar(10) NOT NULL DEFAULT 'center',
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `lastEditedBy` char(30) NOT NULL,
+ `lastEditedDate` datetime NOT NULL,
+ `closedBy` char(30) NOT NULL,
+ `closedDate` datetime NOT NULL,
+ `activatedBy` char(30) NOT NULL,
+ `activatedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_kanbancard` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `kanban` mediumint(8) unsigned NOT NULL,
+ `region` mediumint(8) unsigned NOT NULL,
+ `group` mediumint(8) unsigned NOT NULL,
+ `fromID` mediumint(8) unsigned NOT NULL,
+ `fromType` varchar(30) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `status` varchar(30) NOT NULL DEFAULT 'doing',
+ `pri` mediumint(8) unsigned NOT NULL,
+ `assignedTo` text NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ `estimate` float unsigned NOT NULL,
+ `progress` float unsigned NOT NULL DEFAULT '0',
+ `color` char(7) NOT NULL,
+ `acl` char(30) NOT NULL DEFAULT 'open',
+ `whitelist` text NOT NULL,
+ `order` mediumint(8) NOT NULL DEFAULT '0',
+ `archived` enum('0','1') NOT NULL DEFAULT '0',
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `lastEditedBy` char(30) NOT NULL,
+ `lastEditedDate` datetime NOT NULL,
+ `archivedBy` char(30) NOT NULL,
+ `archivedDate` datetime NOT NULL,
+ `assignedBy` char(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_kanbancell` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `kanban` mediumint(8) NOT NULL,
+ `lane` mediumint(8) NOT NULL,
+ `column` mediumint(8) NOT NULL,
+ `type` char(30) NOT NULL,
+ `cards` text NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `card_group` (`kanban`,`type`,`lane`,`column`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_kanbancolumn` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `parent` mediumint(8) NOT NULL DEFAULT '0',
+ `type` char(30) NOT NULL,
+ `region` mediumint(8) unsigned NOT NULL,
+ `group` mediumint(8) NOT NULL DEFAULT '0',
+ `name` varchar(255) NOT NULL DEFAULT '',
+ `color` char(30) NOT NULL,
+ `limit` smallint(6) NOT NULL DEFAULT '-1',
+ `order` mediumint(8) NOT NULL DEFAULT '0',
+ `archived` enum('0','1') NOT NULL DEFAULT '0',
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_kanbangroup` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `kanban` mediumint(8) unsigned NOT NULL,
+ `region` mediumint(8) unsigned NOT NULL,
+ `order` smallint(6) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_kanbanlane` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `execution` mediumint(8) NOT NULL DEFAULT '0',
+ `type` char(30) NOT NULL,
+ `region` mediumint(8) unsigned NOT NULL,
+ `group` mediumint(8) unsigned NOT NULL,
+ `groupby` char(30) NOT NULL,
+ `extra` char(30) NOT NULL,
+ `name` varchar(255) NOT NULL DEFAULT '',
+ `color` char(30) NOT NULL,
+ `order` smallint(6) NOT NULL DEFAULT '0',
+ `lastEditedTime` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_kanbanregion` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `space` mediumint(8) unsigned NOT NULL,
+ `kanban` mediumint(8) unsigned NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `order` mediumint(8) NOT NULL DEFAULT '0',
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `lastEditedBy` char(30) NOT NULL,
+ `lastEditedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_kanbanspace` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `type` varchar(50) NOT NULL,
+ `owner` varchar(30) NOT NULL,
+ `team` text NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `acl` char(30) NOT NULL DEFAULT 'open',
+ `whitelist` text NOT NULL,
+ `status` enum('active','closed') NOT NULL DEFAULT 'active',
+ `order` mediumint(8) NOT NULL DEFAULT '0',
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `lastEditedBy` char(30) NOT NULL,
+ `lastEditedDate` datetime NOT NULL,
+ `closedBy` char(30) NOT NULL,
+ `closedDate` datetime NOT NULL,
+ `activatedBy` char(30) NOT NULL,
+ `activatedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_lang` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `lang` varchar(30) NOT NULL,
+ `module` varchar(30) NOT NULL,
+ `section` varchar(30) NOT NULL,
+ `key` varchar(60) NOT NULL,
+ `value` text NOT NULL,
+ `system` enum('0','1') NOT NULL DEFAULT '1',
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `lang` (`lang`,`module`,`section`,`key`,`vision`)
+) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_leave` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `year` char(4) NOT NULL,
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ `start` time NOT NULL,
+ `finish` time NOT NULL,
+ `hours` float(4,1) unsigned NOT NULL DEFAULT '0.0',
+ `backDate` datetime NOT NULL,
+ `type` varchar(30) NOT NULL DEFAULT '',
+ `desc` text NOT NULL,
+ `status` varchar(30) NOT NULL DEFAULT '',
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `reviewedBy` char(30) NOT NULL,
+ `reviewedDate` datetime NOT NULL,
+ `level` tinyint(3) NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `reviewers` text NOT NULL,
+ `backReviewers` text NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `year` (`year`),
+ KEY `type` (`type`),
+ KEY `status` (`status`),
+ KEY `createdBy` (`createdBy`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_lieu` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `year` char(4) NOT NULL,
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ `start` time NOT NULL,
+ `finish` time NOT NULL,
+ `hours` float(4,1) unsigned NOT NULL DEFAULT '0.0',
+ `overtime` char(255) NOT NULL,
+ `trip` char(255) NOT NULL,
+ `desc` text NOT NULL,
+ `status` varchar(30) NOT NULL DEFAULT '',
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `reviewedBy` char(30) NOT NULL,
+ `reviewedDate` datetime NOT NULL,
+ `level` tinyint(3) NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `reviewers` text NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `year` (`year`),
+ KEY `status` (`status`),
+ KEY `createdBy` (`createdBy`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_log` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `objectType` varchar(30) NOT NULL,
+ `objectID` mediumint(8) unsigned NOT NULL,
+ `action` mediumint(8) unsigned NOT NULL,
+ `date` datetime NOT NULL,
+ `url` varchar(255) NOT NULL,
+ `contentType` varchar(30) NOT NULL,
+ `data` text NOT NULL,
+ `result` text NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `objectType` (`objectType`),
+ KEY `obejctID` (`objectID`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_measqueue` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `type` varchar(30) NOT NULL,
+ `mid` mediumint(8) unsigned NOT NULL,
+ `status` varchar(255) NOT NULL,
+ `logs` text,
+ `execTime` varchar(10) NOT NULL,
+ `params` text,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `updateDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_measrecords` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `type` varchar(30) NOT NULL,
+ `mid` mediumint(8) NOT NULL,
+ `measCode` char(50) NOT NULL DEFAULT '',
+ `project` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `params` text NOT NULL,
+ `year` char(4) NOT NULL,
+ `month` char(6) NOT NULL,
+ `week` char(8) NOT NULL,
+ `day` char(8) NOT NULL,
+ `value` varchar(255) NOT NULL,
+ `date` date NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `product` (`product`),
+ KEY `project` (`project`),
+ KEY `time` (`year`,`month`,`day`,`week`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_meastemplate` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `model` char(30) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `content` mediumtext NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_meeting` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) NOT NULL,
+ `execution` mediumint(8) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `type` varchar(255) NOT NULL,
+ `begin` time NOT NULL,
+ `end` time NOT NULL,
+ `dept` mediumint(8) NOT NULL,
+ `mode` varchar(255) NOT NULL,
+ `host` varchar(30) NOT NULL,
+ `participant` text NOT NULL,
+ `date` date NOT NULL,
+ `room` int(11) NOT NULL,
+ `minutes` text NOT NULL,
+ `minutedBy` varchar(30) NOT NULL,
+ `minutedDate` datetime NOT NULL,
+ `objectType` varchar(30) NOT NULL,
+ `objectID` mediumint(8) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_meetingroom` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `position` varchar(30) NOT NULL,
+ `seats` int(11) NOT NULL,
+ `equipment` varchar(255) NOT NULL,
+ `openTime` varchar(255) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_module` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `root` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `branch` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `name` char(60) NOT NULL DEFAULT '',
+ `parent` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `path` char(255) NOT NULL DEFAULT '',
+ `grade` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `order` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `type` char(30) NOT NULL,
+ `from` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `owner` varchar(30) NOT NULL,
+ `collector` text NOT NULL,
+ `short` varchar(30) NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `root` (`root`),
+ KEY `type` (`type`),
+ KEY `path` (`path`)
+) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_mr` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `hostID` mediumint(8) unsigned NOT NULL,
+ `sourceProject` varchar(50) NOT NULL,
+ `sourceBranch` varchar(100) NOT NULL,
+ `targetProject` varchar(50) NOT NULL,
+ `targetBranch` varchar(100) NOT NULL,
+ `mriid` int(10) unsigned NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `description` text NOT NULL,
+ `assignee` varchar(255) NOT NULL,
+ `reviewer` varchar(255) NOT NULL,
+ `approver` varchar(255) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ `status` char(30) NOT NULL,
+ `mergeStatus` char(30) NOT NULL,
+ `approvalStatus` char(30) NOT NULL,
+ `needApproved` enum('0','1') NOT NULL DEFAULT '0',
+ `needCI` enum('0','1') NOT NULL DEFAULT '0',
+ `repoID` mediumint(8) unsigned NOT NULL,
+ `jobID` mediumint(8) unsigned NOT NULL,
+ `compileID` mediumint(8) unsigned NOT NULL,
+ `compileStatus` char(30) NOT NULL,
+ `removeSourceBranch` enum('0','1') NOT NULL DEFAULT '0',
+ `synced` enum('0','1') NOT NULL DEFAULT '1',
+ `syncError` varchar(255) NOT NULL,
+ `hasNoConflict` enum('0','1') NOT NULL DEFAULT '0',
+ `diffs` longtext,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_mrapproval` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `mrID` mediumint(8) unsigned NOT NULL,
+ `account` varchar(255) NOT NULL,
+ `date` datetime NOT NULL,
+ `action` char(30) NOT NULL,
+ `comment` text NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_nc` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `auditplan` mediumint(8) NOT NULL,
+ `listID` mediumint(8) NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `type` char(30) NOT NULL,
+ `status` varchar(30) NOT NULL DEFAULT 'active',
+ `severity` char(30) NOT NULL,
+ `deadline` date NOT NULL,
+ `resolvedBy` varchar(30) NOT NULL,
+ `resolution` char(30) NOT NULL,
+ `resolvedDate` date NOT NULL,
+ `closedBy` varchar(30) NOT NULL,
+ `closedDate` date NOT NULL,
+ `parent` mediumint(8) unsigned NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `assignedDate` date NOT NULL,
+ `activateDate` date NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_notify` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `objectType` varchar(50) NOT NULL,
+ `objectID` mediumint(8) unsigned NOT NULL,
+ `action` mediumint(9) NOT NULL,
+ `toList` varchar(255) NOT NULL,
+ `ccList` text NOT NULL,
+ `subject` varchar(255) NOT NULL,
+ `data` text NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `sendTime` datetime NOT NULL,
+ `status` varchar(10) NOT NULL DEFAULT 'wait',
+ `failReason` text NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `objectType_toList_status` (`objectType`,`toList`,`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_oauth` (
+ `account` varchar(30) NOT NULL,
+ `openID` varchar(255) NOT NULL,
+ `providerType` varchar(30) NOT NULL,
+ `providerID` mediumint(8) unsigned NOT NULL,
+ KEY `account` (`account`),
+ KEY `providerType` (`providerType`),
+ KEY `providerID` (`providerID`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_object` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) NOT NULL,
+ `from` mediumint(8) NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `category` char(30) NOT NULL,
+ `version` varchar(255) NOT NULL,
+ `type` enum('reviewed','taged') NOT NULL,
+ `range` text NOT NULL,
+ `data` text NOT NULL,
+ `storyEst` char(30) NOT NULL,
+ `taskEst` char(30) NOT NULL,
+ `requestEst` char(30) NOT NULL,
+ `testEst` char(30) NOT NULL,
+ `devEst` char(30) NOT NULL,
+ `designEst` char(30) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_opportunity` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `source` char(30) NOT NULL,
+ `type` char(30) NOT NULL,
+ `strategy` char(30) NOT NULL,
+ `status` varchar(30) NOT NULL DEFAULT 'active',
+ `impact` mediumint(8) NOT NULL,
+ `chance` mediumint(8) NOT NULL,
+ `ratio` mediumint(8) NOT NULL,
+ `pri` char(30) NOT NULL,
+ `identifiedDate` date NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `assignedDate` date NOT NULL,
+ `approvedDate` date NOT NULL,
+ `prevention` mediumtext NOT NULL,
+ `plannedClosedDate` date NOT NULL,
+ `actualClosedDate` date NOT NULL,
+ `lib` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `from` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `version` smallint(6) NOT NULL DEFAULT '1',
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `activatedBy` varchar(30) NOT NULL,
+ `activatedDate` datetime NOT NULL,
+ `closedBy` varchar(30) NOT NULL,
+ `closedDate` datetime NOT NULL,
+ `canceledBy` varchar(30) NOT NULL,
+ `canceledDate` datetime NOT NULL,
+ `cancelReason` char(30) NOT NULL,
+ `hangupedBy` varchar(30) NOT NULL,
+ `hangupedDate` datetime NOT NULL,
+ `resolution` mediumtext NOT NULL,
+ `resolvedBy` varchar(30) NOT NULL,
+ `resolvedDate` datetime NOT NULL,
+ `lastCheckedBy` varchar(30) NOT NULL,
+ `lastCheckedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_overtime` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `year` char(4) NOT NULL,
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ `start` time NOT NULL,
+ `finish` time NOT NULL,
+ `hours` float(4,1) unsigned NOT NULL DEFAULT '0.0',
+ `leave` varchar(255) NOT NULL,
+ `type` varchar(30) NOT NULL DEFAULT '',
+ `desc` text NOT NULL,
+ `status` varchar(30) NOT NULL DEFAULT '',
+ `rejectReason` varchar(100) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `reviewedBy` char(30) NOT NULL,
+ `reviewedDate` datetime NOT NULL,
+ `level` tinyint(3) NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `reviewers` text NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `year` (`year`),
+ KEY `type` (`type`),
+ KEY `status` (`status`),
+ KEY `createdBy` (`createdBy`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_pipeline` (
+ `id` smallint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `type` char(30) NOT NULL,
+ `name` varchar(50) NOT NULL,
+ `url` varchar(255) DEFAULT NULL,
+ `account` varchar(30) DEFAULT NULL,
+ `password` varchar(255) NOT NULL,
+ `token` varchar(255) DEFAULT NULL,
+ `private` char(32) DEFAULT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_planstory` (
+ `plan` mediumint(8) unsigned NOT NULL,
+ `story` mediumint(8) unsigned NOT NULL,
+ `order` mediumint(9) NOT NULL,
+ UNIQUE KEY `plan_story` (`plan`,`story`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_process` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `model` char(30) NOT NULL DEFAULT 'waterfall',
+ `name` varchar(255) NOT NULL,
+ `type` char(30) NOT NULL,
+ `abbr` char(30) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `order` mediumint(9) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `assignedBy` varchar(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_product` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `program` mediumint(8) unsigned NOT NULL,
+ `name` varchar(90) NOT NULL,
+ `code` varchar(45) NOT NULL,
+ `shadow` tinyint(1) unsigned NOT NULL,
+ `bind` enum('0','1') NOT NULL DEFAULT '0',
+ `line` mediumint(8) NOT NULL,
+ `type` varchar(30) NOT NULL DEFAULT 'normal',
+ `status` varchar(30) NOT NULL DEFAULT '',
+ `subStatus` varchar(30) NOT NULL DEFAULT '',
+ `desc` mediumtext NOT NULL,
+ `PO` varchar(30) NOT NULL,
+ `QD` varchar(30) NOT NULL,
+ `RD` varchar(30) NOT NULL,
+ `feedback` varchar(30) NOT NULL,
+ `ticket` varchar(30) NOT NULL,
+ `acl` enum('open','private','custom') NOT NULL DEFAULT 'open',
+ `whitelist` text NOT NULL,
+ `reviewer` text NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `createdVersion` varchar(20) NOT NULL,
+ `order` mediumint(8) unsigned NOT NULL,
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `acl` (`acl`),
+ KEY `order` (`order`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_productplan` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `product` mediumint(8) unsigned NOT NULL,
+ `branch` varchar(255) NOT NULL DEFAULT '0',
+ `parent` mediumint(9) NOT NULL DEFAULT '0',
+ `title` varchar(90) NOT NULL,
+ `status` enum('wait','doing','done','closed') NOT NULL DEFAULT 'wait',
+ `desc` mediumtext NOT NULL,
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ `order` text NOT NULL,
+ `closedReason` varchar(20) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `product` (`product`),
+ KEY `end` (`end`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_programactivity` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `process` mediumint(8) NOT NULL,
+ `activity` mediumint(8) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `content` text NOT NULL,
+ `reason` varchar(255) NOT NULL,
+ `result` char(30) NOT NULL,
+ `linkedBy` char(30) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_programoutput` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `process` mediumint(8) NOT NULL,
+ `activity` mediumint(8) NOT NULL,
+ `output` mediumint(8) NOT NULL,
+ `content` text NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `reason` varchar(255) NOT NULL,
+ `result` char(30) NOT NULL,
+ `linkedBy` char(30) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_programprocess` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `process` mediumint(8) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `type` char(30) NOT NULL,
+ `abbr` char(30) NOT NULL,
+ `desc` text NOT NULL,
+ `reason` varchar(255) NOT NULL,
+ `linkedBy` char(30) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_programreport` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `template` mediumint(8) NOT NULL,
+ `project` mediumint(8) unsigned NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `params` text NOT NULL,
+ `content` text NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_project` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) NOT NULL DEFAULT '0',
+ `model` char(30) NOT NULL,
+ `type` char(30) NOT NULL DEFAULT 'sprint',
+ `lifetime` char(30) NOT NULL DEFAULT '',
+ `budget` varchar(30) NOT NULL DEFAULT '0',
+ `budgetUnit` char(30) NOT NULL DEFAULT 'CNY',
+ `attribute` varchar(30) NOT NULL DEFAULT '',
+ `percent` float unsigned NOT NULL DEFAULT '0',
+ `milestone` enum('0','1') NOT NULL DEFAULT '0',
+ `output` text NOT NULL,
+ `auth` char(30) NOT NULL,
+ `parent` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `path` varchar(255) NOT NULL,
+ `grade` tinyint(3) unsigned NOT NULL,
+ `name` varchar(90) NOT NULL,
+ `code` varchar(45) NOT NULL,
+ `hasProduct` tinyint(1) unsigned NOT NULL DEFAULT 1,
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ `realBegan` date NOT NULL,
+ `realEnd` date NOT NULL,
+ `days` smallint(5) unsigned NOT NULL,
+ `status` varchar(10) NOT NULL,
+ `subStatus` varchar(30) NOT NULL DEFAULT '',
+ `pri` enum('1','2','3','4') NOT NULL DEFAULT '1',
+ `desc` mediumtext NOT NULL,
+ `version` smallint(6) NOT NULL,
+ `parentVersion` smallint(6) NOT NULL,
+ `planDuration` int(11) NOT NULL,
+ `realDuration` int(11) NOT NULL,
+ `openedBy` varchar(30) NOT NULL DEFAULT '',
+ `openedDate` datetime NOT NULL,
+ `openedVersion` varchar(20) NOT NULL,
+ `lastEditedBy` varchar(30) NOT NULL DEFAULT '',
+ `lastEditedDate` datetime NOT NULL,
+ `closedBy` varchar(30) NOT NULL DEFAULT '',
+ `closedDate` datetime NOT NULL,
+ `canceledBy` varchar(30) NOT NULL DEFAULT '',
+ `canceledDate` datetime NOT NULL,
+ `suspendedDate` date NOT NULL,
+ `PO` varchar(30) NOT NULL DEFAULT '',
+ `PM` varchar(30) NOT NULL DEFAULT '',
+ `QD` varchar(30) NOT NULL DEFAULT '',
+ `RD` varchar(30) NOT NULL DEFAULT '',
+ `team` varchar(90) NOT NULL,
+ `acl` char(30) NOT NULL DEFAULT 'open',
+ `whitelist` text NOT NULL,
+ `order` mediumint(8) unsigned NOT NULL,
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `division` enum('0','1') NOT NULL DEFAULT '1',
+ `displayCards` smallint(6) NOT NULL DEFAULT '0',
+ `fluidBoard` enum('0','1') NOT NULL DEFAULT '0',
+ `multiple` enum('0', '1') NOT NULL DEFAULT '1',
+ `colWidth` smallint(4) NOT NULL DEFAULT '264',
+ `minColWidth` smallint(4) NOT NULL DEFAULT '200',
+ `maxColWidth` smallint(4) NOT NULL DEFAULT '384',
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `parent` (`parent`),
+ KEY `begin` (`begin`),
+ KEY `end` (`end`),
+ KEY `status` (`status`),
+ KEY `acl` (`acl`),
+ KEY `order` (`order`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_projectadmin` (
+ `group` smallint(6) NOT NULL,
+ `account` char(30) NOT NULL,
+ `programs` text NOT NULL,
+ `projects` text NOT NULL,
+ `products` text NOT NULL,
+ `executions` text NOT NULL,
+ UNIQUE KEY `group_account` (`group`, `account`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_projectcase` (
+ `project` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `product` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `case` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `count` mediumint(8) unsigned NOT NULL DEFAULT '1',
+ `version` smallint(6) NOT NULL DEFAULT '1',
+ `order` smallint(6) unsigned NOT NULL,
+ UNIQUE KEY `project` (`project`,`case`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_projectproduct` (
+ `project` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL,
+ `branch` mediumint(8) unsigned NOT NULL,
+ `plan` varchar(255) NOT NULL,
+ PRIMARY KEY (`project`,`product`,`branch`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_projectspec` (
+ `project` mediumint(8) NOT NULL,
+ `version` smallint(6) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `milestone` enum('0','1') NOT NULL DEFAULT '0',
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ UNIQUE KEY `project` (`project`,`version`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_projectstory` (
+ `project` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `product` mediumint(8) unsigned NOT NULL,
+ `branch` mediumint(8) unsigned NOT NULL,
+ `story` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `version` smallint(6) NOT NULL DEFAULT '1',
+ `order` smallint(6) unsigned NOT NULL,
+ UNIQUE KEY `project` (`project`,`story`),
+ KEY `story` (`story`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_relation` (
+ `id` int(8) NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) NOT NULL,
+ `product` mediumint(8) NOT NULL,
+ `execution` mediumint(8) NOT NULL,
+ `AType` char(30) NOT NULL,
+ `AID` mediumint(8) NOT NULL,
+ `AVersion` char(30) NOT NULL,
+ `relation` char(30) NOT NULL,
+ `BType` char(30) NOT NULL,
+ `BID` mediumint(8) NOT NULL,
+ `BVersion` char(30) NOT NULL,
+ `extra` char(30) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `relation` (`product`,`relation`,`AType`,`BType`,`AID`,`BID`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_relationoftasks` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `pretask` mediumint(8) unsigned NOT NULL,
+ `condition` enum('begin','end') NOT NULL,
+ `task` mediumint(8) unsigned NOT NULL,
+ `action` enum('begin','end') NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `relationoftasks` (`execution`,`task`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_release` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` varchar(255) NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `branch` varchar(255) NOT NULL,
+ `shadow` mediumint(8) unsigned NOT NULL default '0',
+ `build` varchar(255) NOT NULL,
+ `name` varchar(255) NOT NULL DEFAULT '',
+ `marker` enum('0','1') NOT NULL DEFAULT '0',
+ `date` date NOT NULL,
+ `stories` text NOT NULL,
+ `bugs` text NOT NULL,
+ `leftBugs` text NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `mailto` text,
+ `notify` varchar(255) DEFAULT NULL,
+ `status` varchar(20) NOT NULL DEFAULT 'normal',
+ `subStatus` varchar(30) NOT NULL DEFAULT '',
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `product` (`product`),
+ KEY `build` (`build`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_repo` (
+ `id` mediumint(9) NOT NULL AUTO_INCREMENT,
+ `product` varchar(255) NOT NULL,
+ `projects` varchar(255) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `path` varchar(255) NOT NULL,
+ `prefix` varchar(100) NOT NULL,
+ `encoding` varchar(20) NOT NULL,
+ `SCM` varchar(10) NOT NULL,
+ `client` varchar(100) NOT NULL,
+ `serviceHost` varchar(50) NOT NULL,
+ `serviceProject` varchar(100) NOT NULL,
+ `commits` mediumint(8) unsigned NOT NULL,
+ `account` varchar(30) NOT NULL,
+ `password` varchar(30) NOT NULL,
+ `encrypt` varchar(30) NOT NULL DEFAULT 'plain',
+ `acl` text NOT NULL,
+ `synced` tinyint(1) NOT NULL DEFAULT '0',
+ `lastSync` datetime NOT NULL,
+ `desc` text NOT NULL,
+ `extra` char(30) NOT NULL,
+ `preMerge` enum('0','1') NOT NULL DEFAULT '0',
+ `job` mediumint(8) unsigned NOT NULL,
+ `fileServerUrl` text,
+ `fileServerAccount` varchar(40) NOT NULL DEFAULT '',
+ `fileServerPassword` varchar(100) NOT NULL DEFAULT '',
+ `deleted` tinyint(1) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_repobranch` (
+ `repo` mediumint(8) unsigned NOT NULL,
+ `revision` mediumint(8) unsigned NOT NULL,
+ `branch` varchar(255) NOT NULL,
+ UNIQUE KEY `repo_revision_branch` (`repo`,`revision`,`branch`),
+ KEY `branch` (`branch`),
+ KEY `revision` (`revision`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_repofiles` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `repo` mediumint(8) unsigned NOT NULL,
+ `revision` mediumint(8) unsigned NOT NULL,
+ `path` varchar(255) NOT NULL,
+ `parent` varchar(255) NOT NULL,
+ `type` varchar(20) NOT NULL,
+ `action` char(1) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `path` (`path`),
+ KEY `parent` (`parent`),
+ KEY `repo` (`repo`),
+ KEY `revision` (`revision`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_repohistory` (
+ `id` mediumint(9) NOT NULL AUTO_INCREMENT,
+ `repo` mediumint(9) NOT NULL,
+ `revision` varchar(40) NOT NULL,
+ `commit` mediumint(8) unsigned NOT NULL,
+ `comment` text NOT NULL,
+ `committer` varchar(100) NOT NULL,
+ `time` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `repo` (`repo`),
+ KEY `revision` (`revision`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_report` (
+ `id` mediumint(9) NOT NULL AUTO_INCREMENT,
+ `code` varchar(100) NOT NULL,
+ `name` text NOT NULL,
+ `module` varchar(100) NOT NULL,
+ `sql` text NOT NULL,
+ `vars` text NOT NULL,
+ `langs` text NOT NULL,
+ `params` text NOT NULL,
+ `step` tinyint(1) NOT NULL DEFAULT '2',
+ `desc` text NOT NULL,
+ `addedBy` char(30) NOT NULL,
+ `addedDate` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `code` (`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_researchplan` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `customer` varchar(255) NOT NULL,
+ `stakeholder` varchar(255) NOT NULL,
+ `objective` varchar(255) NOT NULL,
+ `begin` datetime NOT NULL,
+ `end` datetime NOT NULL,
+ `location` varchar(255) NOT NULL,
+ `team` varchar(255) NOT NULL,
+ `method` enum('','videoConference','interview','questionnaire','telephoneInterview') NOT NULL,
+ `outline` mediumtext NOT NULL,
+ `schedule` mediumtext NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_researchreport` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `relatedPlan` mediumint(8) unsigned NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `author` varchar(30) NOT NULL,
+ `content` mediumtext NOT NULL,
+ `customer` varchar(255) NOT NULL,
+ `researchObjects` varchar(255) NOT NULL,
+ `begin` datetime NOT NULL,
+ `end` datetime NOT NULL,
+ `location` varchar(255) NOT NULL,
+ `method` enum('','videoConference','interview','questionnaire','telephoneInterview') NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_review` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `object` mediumint(8) NOT NULL,
+ `template` mediumint(8) NOT NULL,
+ `doc` mediumint(8) DEFAULT NULL,
+ `docVersion` smallint(6) NOT NULL,
+ `status` char(30) NOT NULL,
+ `reviewedBy` varchar(255) NOT NULL,
+ `auditedBy` varchar(255) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` date NOT NULL,
+ `deadline` date NOT NULL,
+ `lastReviewedBy` varchar(255) DEFAULT NULL,
+ `lastReviewedDate` date NOT NULL,
+ `lastAuditedBy` varchar(255) NOT NULL,
+ `lastAuditedDate` date NOT NULL,
+ `lastEditedBy` varchar(255) NOT NULL,
+ `lastEditedDate` date NOT NULL,
+ `result` char(30) NOT NULL,
+ `auditResult` char(30) NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_reviewcl` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `title` varchar(255) NOT NULL,
+ `object` char(30) NOT NULL,
+ `category` char(30) NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `order` mediumint(8) DEFAULT '0',
+ `status` varchar(30) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `assignedBy` varchar(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_reviewissue` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `review` mediumint(8) NOT NULL,
+ `approval` mediumint(8) NOT NULL,
+ `injection` mediumint(8) NOT NULL,
+ `identify` mediumint(8) NOT NULL,
+ `type` char(30) NOT NULL DEFAULT 'review',
+ `listID` mediumint(8) NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `opinion` varchar(255) NOT NULL,
+ `opinionDate` date NOT NULL,
+ `status` char(30) NOT NULL,
+ `resolution` char(30) NOT NULL,
+ `resolutionBy` char(30) NOT NULL,
+ `resolutionDate` date NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_reviewlist` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `title` varchar(255) NOT NULL,
+ `object` char(30) NOT NULL,
+ `category` char(30) NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `assignedBy` varchar(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_reviewresult` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `review` mediumint(8) NOT NULL,
+ `type` char(30) NOT NULL DEFAULT 'review',
+ `result` char(30) NOT NULL,
+ `opinion` text NOT NULL,
+ `reviewer` char(30) NOT NULL,
+ `remainIssue` char(30) NOT NULL,
+ `createdDate` date NOT NULL,
+ `consumed` float NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `reviewer` (`review`,`reviewer`,`type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_risk` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` varchar(255) NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `source` char(30) NOT NULL,
+ `category` char(30) NOT NULL,
+ `strategy` char(30) NOT NULL,
+ `status` varchar(30) NOT NULL DEFAULT 'active',
+ `impact` char(30) NOT NULL,
+ `probability` char(30) NOT NULL,
+ `rate` char(30) NOT NULL,
+ `pri` char(30) NOT NULL,
+ `identifiedDate` date NOT NULL,
+ `prevention` mediumtext NOT NULL,
+ `remedy` mediumtext NOT NULL,
+ `plannedClosedDate` date NOT NULL,
+ `actualClosedDate` date NOT NULL,
+ `lib` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `from` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `version` smallint(6) NOT NULL DEFAULT '1',
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `resolution` mediumtext NOT NULL,
+ `resolvedBy` varchar(30) NOT NULL,
+ `activateBy` varchar(30) NOT NULL,
+ `activateDate` date NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `closedBy` varchar(30) NOT NULL,
+ `closedDate` date NOT NULL,
+ `cancelBy` varchar(30) NOT NULL,
+ `cancelDate` date NOT NULL,
+ `cancelReason` char(30) NOT NULL,
+ `hangupBy` varchar(30) NOT NULL,
+ `hangupDate` date NOT NULL,
+ `trackedBy` varchar(30) NOT NULL,
+ `trackedDate` date NOT NULL,
+ `assignedDate` date NOT NULL,
+ `approvedDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_riskissue` (
+ `risk` mediumint(8) unsigned NOT NULL,
+ `issue` mediumint(8) unsigned NOT NULL,
+ UNIQUE KEY `risk_issue` (`risk`,`issue`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_score` (
+ `id` bigint(12) unsigned NOT NULL AUTO_INCREMENT,
+ `account` varchar(30) NOT NULL,
+ `module` varchar(30) NOT NULL DEFAULT '',
+ `method` varchar(30) NOT NULL,
+ `desc` varchar(250) NOT NULL DEFAULT '',
+ `before` int(11) NOT NULL DEFAULT '0',
+ `score` int(11) NOT NULL DEFAULT '0',
+ `after` int(11) NOT NULL DEFAULT '0',
+ `time` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `account` (`account`),
+ KEY `model` (`module`),
+ KEY `method` (`method`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_searchdict` (
+ `key` smallint(5) unsigned NOT NULL,
+ `value` char(3) NOT NULL,
+ PRIMARY KEY (`key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_searchindex` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `objectType` char(20) NOT NULL,
+ `objectID` mediumint(9) NOT NULL,
+ `title` text NOT NULL,
+ `content` text NOT NULL,
+ `addedDate` datetime NOT NULL,
+ `editedDate` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `object` (`objectType`,`objectID`),
+ KEY `addedDate` (`addedDate`),
+ FULLTEXT KEY `content` (`content`),
+ FULLTEXT KEY `title` (`title`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_serverroom` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(128) NOT NULL,
+ `city` varchar(128) NOT NULL,
+ `line` varchar(20) NOT NULL,
+ `bandwidth` varchar(128) NOT NULL,
+ `provider` varchar(128) NOT NULL,
+ `owner` varchar(30) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` char(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_service` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `external` enum('0','1') NOT NULL DEFAULT '0',
+ `port` smallint(5) unsigned NOT NULL,
+ `entry` varchar(255) NOT NULL,
+ `deploy` varchar(255) NOT NULL,
+ `version` varchar(64) NOT NULL,
+ `color` char(7) NOT NULL,
+ `desc` mediumtext,
+ `dept` varchar(128) NOT NULL,
+ `devel` varchar(30) NOT NULL,
+ `qa` varchar(30) NOT NULL,
+ `ops` varchar(30) NOT NULL,
+ `hosts` text,
+ `softName` varchar(128) NOT NULL,
+ `softVersion` varchar(128) NOT NULL,
+ `type` varchar(20) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` char(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `parent` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `path` char(255) NOT NULL DEFAULT '',
+ `grade` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `order` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_solutions` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `contents` text NOT NULL COMMENT '问题描述',
+ `support` text NOT NULL COMMENT '是否需要高层支持',
+ `measures` text NOT NULL COMMENT '解决建议',
+ `type` char(30) NOT NULL,
+ `addedBy` varchar(30) NOT NULL,
+ `addedDate` date NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_sqlview` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(90) NOT NULL,
+ `code` varchar(45) NOT NULL,
+ `sql` text NOT NULL,
+ `desc` text NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_stage` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `percent` varchar(255) NOT NULL,
+ `type` varchar(255) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_stakeholder` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ `objectID` mediumint(8) NOT NULL,
+ `objectType` char(30) NOT NULL,
+ `user` char(30) NOT NULL,
+ `type` char(30) NOT NULL,
+ `key` enum('0','1') NOT NULL,
+ `from` char(30) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` date NOT NULL,
+ `editedBy` char(30) NOT NULL,
+ `editedDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL,
+ KEY `objectID` (`objectID`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_story` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `parent` mediumint(9) NOT NULL DEFAULT '0',
+ `product` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `branch` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `module` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `plan` text,
+ `source` varchar(20) NOT NULL,
+ `sourceNote` varchar(255) NOT NULL,
+ `fromBug` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `feedback` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `title` varchar(255) NOT NULL,
+ `keywords` varchar(255) NOT NULL,
+ `type` varchar(30) NOT NULL DEFAULT 'story',
+ `category` varchar(30) NOT NULL DEFAULT 'feature',
+ `pri` tinyint(3) unsigned NOT NULL DEFAULT '3',
+ `estimate` float unsigned NOT NULL,
+ `status` enum('','changing','active','draft','closed','reviewing') NOT NULL DEFAULT '',
+ `subStatus` varchar(30) NOT NULL DEFAULT '',
+ `color` char(7) NOT NULL,
+ `stage` enum('','wait','planned','projected','developing','developed','testing','tested','verified','released','closed') NOT NULL DEFAULT 'wait',
+ `stagedBy` char(30) NOT NULL,
+ `mailto` text,
+ `lib` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `fromStory` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `fromVersion` smallint(6) NOT NULL DEFAULT '1',
+ `openedBy` varchar(30) NOT NULL DEFAULT '',
+ `openedDate` datetime NOT NULL,
+ `assignedTo` varchar(30) NOT NULL DEFAULT '',
+ `assignedDate` datetime NOT NULL,
+ `approvedDate` date NOT NULL,
+ `lastEditedBy` varchar(30) NOT NULL DEFAULT '',
+ `lastEditedDate` datetime NOT NULL,
+ `changedBy` VARCHAR(30) NOT NULL,
+ `changedDate` DATETIME NOT NULL,
+ `reviewedBy` varchar(255) NOT NULL,
+ `reviewedDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `closedBy` varchar(30) NOT NULL DEFAULT '',
+ `closedDate` datetime NOT NULL,
+ `closedReason` varchar(30) NOT NULL,
+ `activatedDate` datetime NOT NULL,
+ `toBug` mediumint(8) unsigned NOT NULL,
+ `childStories` varchar(255) NOT NULL,
+ `linkStories` varchar(255) NOT NULL,
+ `linkRequirements` varchar(255) NOT NULL,
+ `twins` varchar(255) NOT NULL,
+ `duplicateStory` mediumint(8) unsigned NOT NULL,
+ `version` smallint(6) NOT NULL DEFAULT '1',
+ `storyChanged` enum('0','1') NOT NULL DEFAULT '0',
+ `feedbackBy` varchar(100) NOT NULL,
+ `notifyEmail` varchar(100) NOT NULL,
+ `URChanged` enum('0','1') NOT NULL DEFAULT '0',
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `product` (`product`),
+ KEY `status` (`status`),
+ KEY `assignedTo` (`assignedTo`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_storyestimate` (
+ `story` mediumint(9) NOT NULL,
+ `round` smallint(6) NOT NULL,
+ `estimate` text NOT NULL,
+ `average` float NOT NULL,
+ `openedBy` varchar(30) NOT NULL,
+ `openedDate` datetime NOT NULL,
+ UNIQUE KEY `story` (`story`,`round`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_storyreview` (
+ `story` mediumint(9) NOT NULL,
+ `version` smallint(6) NOT NULL,
+ `reviewer` varchar(30) NOT NULL,
+ `result` varchar(30) NOT NULL,
+ `reviewDate` datetime NOT NULL,
+ UNIQUE KEY `story` (`story`,`version`,`reviewer`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_storyspec` (
+ `story` mediumint(9) NOT NULL,
+ `version` smallint(6) NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `spec` mediumtext NOT NULL,
+ `verify` mediumtext NOT NULL,
+ `files` text NOT NULL,
+ UNIQUE KEY `story` (`story`,`version`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_storystage` (
+ `story` mediumint(8) unsigned NOT NULL,
+ `branch` mediumint(8) unsigned NOT NULL,
+ `stage` varchar(50) NOT NULL,
+ `stagedBy` char(30) NOT NULL,
+ UNIQUE KEY `story_branch` (`story`,`branch`),
+ KEY `story` (`story`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_suitecase` (
+ `suite` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL,
+ `case` mediumint(8) unsigned NOT NULL,
+ `version` smallint(5) unsigned NOT NULL,
+ UNIQUE KEY `suitecase` (`suite`,`case`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_task` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `parent` mediumint(8) NOT NULL DEFAULT '0',
+ `execution` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `module` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `design` mediumint(8) unsigned NOT NULL,
+ `story` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `storyVersion` smallint(6) NOT NULL DEFAULT '1',
+ `designVersion` smallint(6) unsigned NOT NULL,
+ `fromBug` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `fromIssue` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `feedback` mediumint(8) unsigned NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `type` varchar(20) NOT NULL,
+ `mode` varchar(10) NOT NULL,
+ `pri` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `estimate` float unsigned NOT NULL,
+ `consumed` float unsigned NOT NULL,
+ `left` float unsigned NOT NULL,
+ `deadline` date NOT NULL,
+ `status` enum('wait','doing','done','pause','cancel','closed') NOT NULL DEFAULT 'wait',
+ `subStatus` varchar(30) NOT NULL DEFAULT '',
+ `color` char(7) NOT NULL,
+ `mailto` text,
+ `desc` mediumtext NOT NULL,
+ `version` smallint(6) NOT NULL,
+ `openedBy` varchar(30) NOT NULL,
+ `openedDate` datetime NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `estStarted` date NOT NULL,
+ `realStarted` datetime NOT NULL,
+ `finishedBy` varchar(30) NOT NULL,
+ `finishedDate` datetime NOT NULL,
+ `finishedList` text NOT NULL,
+ `canceledBy` varchar(30) NOT NULL,
+ `canceledDate` datetime NOT NULL,
+ `closedBy` varchar(30) NOT NULL,
+ `closedDate` datetime NOT NULL,
+ `planDuration` int(11) NOT NULL,
+ `realDuration` int(11) NOT NULL,
+ `closedReason` varchar(30) NOT NULL,
+ `lastEditedBy` varchar(30) NOT NULL,
+ `lastEditedDate` datetime NOT NULL,
+ `activatedDate` datetime NOT NULL,
+ `order` mediumint(8) NOT NULL DEFAULT 0,
+ `repo` mediumint(8) unsigned NOT NULL,
+ `mr` mediumint(8) unsigned NOT NULL,
+ `entry` varchar(255) NOT NULL,
+ `lines` varchar(10) NOT NULL,
+ `v1` varchar(40) NOT NULL,
+ `v2` varchar(40) NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ PRIMARY KEY (`id`),
+ KEY `execution` (`execution`),
+ KEY `story` (`story`),
+ KEY `parent` (`parent`),
+ KEY `assignedTo` (`assignedTo`),
+ KEY `order` (`order`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_taskestimate` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `task` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `date` date NOT NULL,
+ `left` float unsigned NOT NULL DEFAULT '0',
+ `consumed` float unsigned NOT NULL,
+ `account` char(30) NOT NULL DEFAULT '',
+ `work` text,
+ `order` tinyint unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `task` (`task`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_taskspec` (
+ `task` mediumint(8) NOT NULL,
+ `version` smallint(6) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `estStarted` date NOT NULL,
+ `deadline` date NOT NULL,
+ UNIQUE KEY `task` (`task`,`version`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_taskteam` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `task` mediumint(8) unsigned NOT NULL,
+ `account` char(30) NOT NULL,
+ `estimate` decimal(12,2) NOT NULL,
+ `consumed` decimal(12,2) NOT NULL,
+ `left` decimal(12,2) NOT NULL,
+ `transfer` char(30) NOT NULL,
+ `status` enum('wait','doing','done') NOT NULL DEFAULT 'wait',
+ `order` tinyint(3) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `task` (`task`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_team` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `root` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `type` enum('project','task','execution') NOT NULL DEFAULT 'project',
+ `account` char(30) NOT NULL DEFAULT '',
+ `role` char(30) NOT NULL DEFAULT '',
+ `position` varchar(30) NOT NULL,
+ `limited` char(8) NOT NULL DEFAULT 'no',
+ `join` date NOT NULL DEFAULT '0000-00-00',
+ `days` smallint(5) unsigned NOT NULL,
+ `hours` float(3,1) unsigned NOT NULL DEFAULT '0.0',
+ `estimate` decimal(12,2) unsigned NOT NULL DEFAULT '0.00',
+ `consumed` decimal(12,2) unsigned NOT NULL DEFAULT '0.00',
+ `left` decimal(12,2) unsigned NOT NULL DEFAULT '0.00',
+ `order` tinyint(3) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `team` (`root`,`type`,`account`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_testreport` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL,
+ `tasks` varchar(255) NOT NULL,
+ `builds` varchar(255) NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ `owner` char(30) NOT NULL,
+ `members` text NOT NULL,
+ `stories` text NOT NULL,
+ `bugs` text NOT NULL,
+ `cases` text NOT NULL,
+ `report` text NOT NULL,
+ `objectType` varchar(20) NOT NULL,
+ `objectID` mediumint(8) unsigned NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_testresult` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `run` mediumint(8) unsigned NOT NULL,
+ `case` mediumint(8) unsigned NOT NULL,
+ `version` smallint(5) unsigned NOT NULL,
+ `job` mediumint(8) unsigned NOT NULL,
+ `compile` mediumint(8) unsigned NOT NULL,
+ `caseResult` char(30) NOT NULL,
+ `stepResults` text NOT NULL,
+ `lastRunner` varchar(30) NOT NULL,
+ `date` datetime NOT NULL,
+ `duration` float NOT NULL,
+ `xml` text NOT NULL,
+ `deploy` mediumint(8) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `case` (`case`),
+ KEY `version` (`version`),
+ KEY `run` (`run`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_testrun` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `task` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `case` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `version` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `assignedTo` char(30) NOT NULL DEFAULT '',
+ `lastRunner` varchar(30) NOT NULL,
+ `lastRunDate` datetime NOT NULL,
+ `lastRunResult` char(30) NOT NULL,
+ `status` char(30) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `task` (`task`,`case`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_testsuite` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `type` varchar(20) NOT NULL,
+ `order` SMALLINT UNSIGNED NOT NULL DEFAULT '0',
+ `addedBy` char(30) NOT NULL,
+ `addedDate` datetime NOT NULL,
+ `lastEditedBy` char(30) NOT NULL,
+ `lastEditedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `product` (`product`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_testtask` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `product` mediumint(8) unsigned NOT NULL,
+ `name` char(90) NOT NULL,
+ `execution` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `build` char(30) NOT NULL,
+ `type` varchar(255) NOT NULL DEFAULT '',
+ `owner` varchar(30) NOT NULL,
+ `pri` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ `realFinishedDate` datetime NOT NULL,
+ `mailto` text,
+ `desc` mediumtext NOT NULL,
+ `report` text NOT NULL,
+ `status` enum('blocked','doing','wait','done') NOT NULL DEFAULT 'wait',
+ `testreport` mediumint(8) unsigned NOT NULL,
+ `auto` varchar(10) NOT NULL DEFAULT 'no',
+ `subStatus` varchar(30) NOT NULL DEFAULT '',
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `product` (`product`),
+ KEY `build` (`build`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE IF NOT EXISTS `zt_ticket` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `product` mediumint(8) unsigned NOT NULL,
+ `module` mediumint(8) unsigned NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `type` varchar(30) NOT NULL,
+ `desc` text NOT NULL,
+ `openedBuild` varchar(255) NOT NULL,
+ `feedback` mediumint(8) NOT NULL,
+ `assignedTo` varchar(255) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `realStarted` datetime NOT NULL,
+ `startedBy` varchar(255) NOT NULL,
+ `startedDate` datetime NOT NULL,
+ `deadline` date NOT NULL,
+ `pri` tinyint unsigned NOT NULL DEFAULT '0',
+ `estimate` float unsigned NOT NULL,
+ `consumed` float unsigned NOT NULL,
+ `left` float unsigned NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `openedBy` varchar(30) NOT NULL,
+ `openedDate` datetime NOT NULL,
+ `activatedCount` int(10) NOT NULL,
+ `activatedBy` varchar(30) NOT NULL,
+ `activatedDate` datetime NOT NULL,
+ `closedBy` varchar(30) NOT NULL,
+ `closedDate` datetime NOT NULL,
+ `closedReason` varchar(30) NOT NULL,
+ `finishedBy` varchar(30) NOT NULL,
+ `finishedDate` datetime NOT NULL,
+ `resolvedBy` varchar(30) NOT NULL,
+ `resolvedDate` datetime NOT NULL,
+ `resolution` varchar(1000) NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `keywords` varchar(255) NOT NULL,
+ `repeatTicket` mediumint(8) NOT NULL DEFAULT '0',
+ `mailto` varchar(255) NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ key `product` (`product`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE IF NOT EXISTS `zt_ticketsource` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `ticketId` mediumint(8) unsigned NOT NULL,
+ `customer` varchar(100) NOT NULL,
+ `contact` varchar(100) NOT NULL,
+ `notifyEmail` varchar(100) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ key `ticketId` (`ticketId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_ticketrelation` (
+ `id` mediumint unsigned NOT NULL AUTO_INCREMENT,
+ `ticketId` mediumint unsigned NOT NULL,
+ `objectId` mediumint NOT NULL,
+ `objectType` varchar(100) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `ticketId` (`ticketId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_todo` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `account` char(30) NOT NULL,
+ `date` date NOT NULL,
+ `begin` smallint(4) unsigned zerofill NOT NULL,
+ `end` smallint(4) unsigned zerofill NOT NULL,
+ `feedback` mediumint(8) unsigned NOT NULL,
+ `type` char(15) NOT NULL,
+ `cycle` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `idvalue` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `pri` tinyint(3) unsigned NOT NULL,
+ `name` char(150) NOT NULL,
+ `desc` mediumtext NOT NULL,
+ `status` enum('wait','doing','done','closed') NOT NULL DEFAULT 'wait',
+ `private` tinyint(1) NOT NULL,
+ `config` varchar(255) NOT NULL,
+ `assignedTo` varchar(30) NOT NULL DEFAULT '',
+ `assignedBy` varchar(30) NOT NULL DEFAULT '',
+ `assignedDate` datetime NOT NULL,
+ `finishedBy` varchar(30) NOT NULL DEFAULT '',
+ `finishedDate` datetime NOT NULL,
+ `closedBy` varchar(30) NOT NULL DEFAULT '',
+ `closedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ PRIMARY KEY (`id`),
+ KEY `account` (`account`),
+ KEY `assignedTo` (`assignedTo`),
+ KEY `finishedBy` (`finishedBy`),
+ KEY `date` (`date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_traincategory` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` char(30) NOT NULL DEFAULT '',
+ `parent` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `path` char(255) NOT NULL DEFAULT '',
+ `grade` tinyint(3) NOT NULL,
+ `order` mediumint(8) NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `parent` (`parent`),
+ KEY `path` (`path`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_traincontents` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `code` varchar(50) NOT NULL,
+ `course` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `name` varchar(255) NOT NULL,
+ `type` varchar(30) NOT NULL,
+ `parent` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `path` char(255) NOT NULL DEFAULT '',
+ `desc` text NOT NULL,
+ `order` mediumint(8) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` tinyint(1) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_traincourse` (
+ `id` mediumint(8) NOT NULL AUTO_INCREMENT,
+ `code` varchar(50) NOT NULL,
+ `category` mediumint(8) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `status` varchar(10) NOT NULL,
+ `teacher` varchar(30) NOT NULL DEFAULT '',
+ `desc` mediumtext NOT NULL,
+ `createdBy` varchar(255) NOT NULL,
+ `createdDate` date NOT NULL,
+ `editedBy` varchar(255) NOT NULL,
+ `editedDate` date NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_trainplan` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ `place` varchar(255) NOT NULL,
+ `trainee` text NOT NULL,
+ `lecturer` varchar(20) NOT NULL,
+ `type` enum('inside','outside') NOT NULL DEFAULT 'inside',
+ `status` varchar(20) NOT NULL,
+ `summary` mediumtext NOT NULL,
+ `createdBy` char(30) DEFAULT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_trainrecords` (
+ `user` char(30) NOT NULL,
+ `objectId` mediumint(8) unsigned NOT NULL,
+ `objectType` varchar(10) NOT NULL,
+ `status` varchar(10) NOT NULL,
+ PRIMARY KEY (`user`,`objectId`,`objectType`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_trip` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `type` enum('trip','egress') NOT NULL DEFAULT 'trip',
+ `customers` varchar(20) NOT NULL,
+ `name` char(30) NOT NULL,
+ `desc` text NOT NULL,
+ `year` char(4) NOT NULL,
+ `begin` date NOT NULL,
+ `end` date NOT NULL,
+ `start` time NOT NULL,
+ `finish` time NOT NULL,
+ `from` char(50) NOT NULL,
+ `to` char(50) NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `year` (`year`),
+ KEY `createdBy` (`createdBy`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_user` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `company` mediumint(8) unsigned NOT NULL,
+ `type` char(30) NOT NULL DEFAULT 'inside',
+ `dept` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `account` char(30) NOT NULL DEFAULT '',
+ `password` char(32) NOT NULL DEFAULT '',
+ `role` char(10) NOT NULL DEFAULT '',
+ `realname` varchar(100) NOT NULL DEFAULT '',
+ `pinyin` varchar(255) NOT NULL DEFAULT '',
+ `nickname` char(60) NOT NULL DEFAULT '',
+ `commiter` varchar(100) NOT NULL,
+ `avatar` text NOT NULL,
+ `birthday` date NOT NULL DEFAULT '0000-00-00',
+ `gender` enum('f','m') NOT NULL DEFAULT 'f',
+ `email` char(90) NOT NULL DEFAULT '',
+ `skype` char(90) NOT NULL DEFAULT '',
+ `qq` char(20) NOT NULL DEFAULT '',
+ `mobile` char(11) NOT NULL DEFAULT '',
+ `phone` char(20) NOT NULL DEFAULT '',
+ `weixin` varchar(90) NOT NULL DEFAULT '',
+ `dingding` varchar(90) NOT NULL DEFAULT '',
+ `slack` varchar(90) NOT NULL DEFAULT '',
+ `whatsapp` varchar(90) NOT NULL DEFAULT '',
+ `address` char(120) NOT NULL DEFAULT '',
+ `zipcode` char(10) NOT NULL DEFAULT '',
+ `nature` text NOT NULL,
+ `analysis` text NOT NULL,
+ `strategy` text NOT NULL,
+ `join` date NOT NULL DEFAULT '0000-00-00',
+ `visits` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `visions` varchar(20) NOT NULL DEFAULT 'rnd,lite',
+ `ip` char(15) NOT NULL DEFAULT '',
+ `last` int(10) unsigned NOT NULL DEFAULT '0',
+ `fails` tinyint(5) NOT NULL DEFAULT '0',
+ `locked` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `feedback` enum('0','1') NOT NULL DEFAULT '0',
+ `ranzhi` char(30) NOT NULL DEFAULT '',
+ `ldap` char(30) NOT NULL,
+ `score` int(11) NOT NULL DEFAULT '0',
+ `scoreLevel` int(11) NOT NULL DEFAULT '0',
+ `resetToken` varchar(50) NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ `clientStatus` enum('online','away','busy','offline','meeting') NOT NULL DEFAULT 'offline',
+ `clientLang` varchar(10) NOT NULL DEFAULT 'zh-cn',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `account` (`account`),
+ KEY `dept` (`dept`),
+ KEY `email` (`email`),
+ KEY `commiter` (`commiter`),
+ KEY `deleted` (`deleted`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_usercontact` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `account` char(30) NOT NULL,
+ `listName` varchar(60) NOT NULL,
+ `userList` text NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `account` (`account`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_usergroup` (
+ `account` char(30) NOT NULL DEFAULT '',
+ `group` mediumint(8) unsigned NOT NULL DEFAULT '0',
+ `project` text NOT NULL,
+ UNIQUE KEY `account` (`account`,`group`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_userquery` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `account` char(30) NOT NULL,
+ `module` varchar(30) NOT NULL,
+ `title` varchar(90) NOT NULL,
+ `form` text NOT NULL,
+ `sql` text NOT NULL,
+ `shortcut` enum('0','1') NOT NULL DEFAULT '0',
+ `common` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `account` (`account`),
+ KEY `module` (`module`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_usertpl` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `account` char(30) NOT NULL,
+ `type` char(30) NOT NULL,
+ `title` varchar(150) NOT NULL,
+ `content` text NOT NULL,
+ `public` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `account` (`account`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_userview` (
+ `account` char(30) NOT NULL,
+ `programs` mediumtext NOT NULL,
+ `products` mediumtext NOT NULL,
+ `projects` mediumtext NOT NULL,
+ `sprints` mediumtext NOT NULL,
+ UNIQUE KEY `account` (`account`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_vm` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `hostID` int(10) unsigned NOT NULL DEFAULT '0',
+ `name` varchar(255) NOT NULL DEFAULT '',
+ `osCategory` varchar(50) NOT NULL DEFAULT '',
+ `osType` varchar(50) NOT NULL DEFAULT '',
+ `osArch` varchar(50) NOT NULL DEFAULT '',
+ `osLang` varchar(50) NOT NULL DEFAULT '',
+ `osCpu` tinyint(2) NOT NULL DEFAULT 0,
+ `osMemory` smallint(6) NOT NULL DEFAULT 0,
+ `osDisk` smallint(6) NOT NULL DEFAULT 0,
+ `status` varchar(50) NOT NULL DEFAULT '',
+ `destroyAt` datetime DEFAULT NULL,
+ `macAddress` varchar(255) NOT NULL DEFAULT '',
+ `workspace` varchar(255) NOT NULL DEFAULT '',
+ `templateID` int(10) unsigned NOT NULL DEFAULT '0',
+ `baseImageID` int(10) unsigned NOT NULL DEFAULT '0',
+ `baseImagePath` varchar(255) NOT NULL DEFAULT '',
+ `desc` varchar(255) NOT NULL DEFAULT '',
+ `heatbeat` datetime DEFAULT NULL,
+ `vncPort` int(10) NOT NULL DEFAULT 0,
+ `instance` varchar(255) NOT NULL DEFAULT '',
+ `eip` varchar(255) NOT NULL DEFAULT '',
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ `public` varchar(50) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_vmtemplate` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `hostID` int(10) unsigned NOT NULL DEFAULT '0',
+ `templateName` varchar(255) NOT NULL DEFAULT '',
+ `osType` varchar(50) NOT NULL DEFAULT '',
+ `osCategory` varchar(50) NOT NULL DEFAULT '',
+ `osVersion` varchar(50) NOT NULL DEFAULT '',
+ `osLang` varchar(50) NOT NULL,
+ `cpuCoreNum` smallint(4) NOT NULL DEFAULT 0,
+ `memorySize` int NOT NULL DEFAULT 0,
+ `diskSize` int NOT NULL DEFAULT 0,
+ `osArch` varchar(50) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_webhook` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `type` varchar(15) NOT NULL DEFAULT 'default',
+ `name` varchar(50) NOT NULL,
+ `url` varchar(255) NOT NULL,
+ `domain` varchar(255) NOT NULL,
+ `secret` varchar(255) NOT NULL,
+ `contentType` varchar(30) NOT NULL DEFAULT 'application/json',
+ `sendType` enum('sync','async') NOT NULL DEFAULT 'sync',
+ `products` text NOT NULL,
+ `executions` text NOT NULL,
+ `params` varchar(100) NOT NULL,
+ `actions` text NOT NULL,
+ `desc` text NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_weeklyreport` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `weekStart` date NOT NULL,
+ `pv` float(9,2) NOT NULL,
+ `ev` float(9,2) NOT NULL,
+ `ac` float(9,2) NOT NULL,
+ `sv` float(9,2) NOT NULL,
+ `cv` float(9,2) NOT NULL,
+ `staff` smallint(5) unsigned NOT NULL,
+ `progress` varchar(255) NOT NULL,
+ `workload` varchar(255) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `week` (`project`,`weekStart`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workestimation` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `project` mediumint(8) unsigned NOT NULL,
+ `scale` decimal(10,2) unsigned NOT NULL,
+ `productivity` decimal(10,2) unsigned NOT NULL,
+ `duration` decimal(10,2) unsigned NOT NULL,
+ `unitLaborCost` decimal(10,2) unsigned NOT NULL,
+ `totalLaborCost` decimal(10,2) unsigned NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `assignedTo` varchar(30) NOT NULL,
+ `assignedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ `dayHour` decimal(10,2) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workflow` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `parent` varchar(30) NOT NULL,
+ `child` varchar(30) NOT NULL,
+ `type` varchar(10) NOT NULL DEFAULT 'flow',
+ `navigator` varchar(10) NOT NULL,
+ `app` varchar(20) NOT NULL,
+ `position` varchar(30) NOT NULL,
+ `module` varchar(30) NOT NULL,
+ `table` varchar(50) NOT NULL,
+ `name` varchar(30) NOT NULL,
+ `titleField` varchar(30) NOT NULL,
+ `contentField` text NOT NULL,
+ `flowchart` text NOT NULL,
+ `js` text NOT NULL,
+ `css` text NOT NULL,
+ `order` smallint(5) unsigned NOT NULL,
+ `buildin` tinyint(1) unsigned NOT NULL,
+ `administrator` text NOT NULL,
+ `desc` text NOT NULL,
+ `version` varchar(10) NOT NULL DEFAULT '1.0',
+ `status` varchar(10) NOT NULL DEFAULT 'wait',
+ `approval` enum('enabled', 'disabled') NOT NULL DEFAULT 'disabled',
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `unique` (`app`,`module`,`vision`),
+ KEY `type` (`type`),
+ KEY `app` (`app`),
+ KEY `module` (`module`),
+ KEY `order` (`order`)
+) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workflowaction` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `module` varchar(30) NOT NULL,
+ `action` varchar(50) NOT NULL,
+ `method` varchar(50) NOT NULL,
+ `name` varchar(50) NOT NULL,
+ `type` enum('single','batch') NOT NULL DEFAULT 'single',
+ `batchMode` enum('same','different') NOT NULL DEFAULT 'different',
+ `extensionType` varchar(10) NOT NULL DEFAULT 'override' COMMENT 'none | extend | override',
+ `open` varchar(20) NOT NULL,
+ `position` enum('menu','browseandview','browse','view') NOT NULL DEFAULT 'browseandview',
+ `layout` char(20) NOT NULL,
+ `show` enum('dropdownlist','direct') NOT NULL DEFAULT 'dropdownlist',
+ `order` smallint(5) unsigned NOT NULL,
+ `buildin` tinyint(1) unsigned NOT NULL,
+ `role` varchar(10) NOT NULL DEFAULT 'custom',
+ `virtual` tinyint(1) unsigned NOT NULL,
+ `conditions` text NOT NULL,
+ `verifications` text NOT NULL,
+ `hooks` text NOT NULL,
+ `linkages` text NOT NULL,
+ `js` text NOT NULL,
+ `css` text NOT NULL,
+ `toList` char(255) NOT NULL,
+ `blocks` text NOT NULL,
+ `desc` text NOT NULL,
+ `status` varchar(10) NOT NULL DEFAULT 'enable',
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `unique` (`module`,`action`,`vision`),
+ KEY `module` (`module`),
+ KEY `action` (`action`),
+ KEY `order` (`order`)
+) ENGINE=InnoDB AUTO_INCREMENT=190 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workflowdatasource` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `type` enum('system','sql','func','option','lang','category') NOT NULL DEFAULT 'option',
+ `name` varchar(30) NOT NULL,
+ `code` varchar(30) NOT NULL,
+ `datasource` text NOT NULL,
+ `view` varchar(20) NOT NULL,
+ `keyField` varchar(50) NOT NULL,
+ `valueField` varchar(50) NOT NULL,
+ `buildin` tinyint(1) unsigned NOT NULL,
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` char(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `type` (`type`)
+) ENGINE=InnoDB AUTO_INCREMENT=71 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workflowfield` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `module` varchar(30) NOT NULL,
+ `field` varchar(50) NOT NULL,
+ `type` varchar(20) NOT NULL DEFAULT 'varchar',
+ `length` varchar(10) NOT NULL,
+ `name` varchar(50) NOT NULL,
+ `control` varchar(20) NOT NULL,
+ `expression` text NOT NULL,
+ `options` text NOT NULL,
+ `default` varchar(100) NOT NULL,
+ `rules` varchar(255) NOT NULL,
+ `placeholder` varchar(100) NOT NULL,
+ `order` smallint(5) unsigned NOT NULL,
+ `searchOrder` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `exportOrder` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `canExport` enum('0','1') NOT NULL DEFAULT '0',
+ `canSearch` enum('0','1') NOT NULL DEFAULT '0',
+ `isValue` enum('0','1') NOT NULL DEFAULT '0',
+ `readonly` enum('0','1') NOT NULL DEFAULT '0',
+ `buildin` tinyint(1) unsigned NOT NULL,
+ `role` varchar(10) NOT NULL DEFAULT 'custom',
+ `desc` text NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `unique` (`module`,`field`),
+ KEY `module` (`module`),
+ KEY `field` (`field`),
+ KEY `order` (`order`)
+) ENGINE=InnoDB AUTO_INCREMENT=360 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workflowlabel` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `module` varchar(30) NOT NULL,
+ `action` varchar(30) NOT NULL DEFAULT 'browse',
+ `code` varchar(30) NOT NULL,
+ `label` varchar(255) NOT NULL,
+ `params` text NOT NULL,
+ `orderBy` text NOT NULL,
+ `order` tinyint(3) NOT NULL,
+ `buildin` tinyint(1) unsigned NOT NULL,
+ `role` varchar(10) NOT NULL DEFAULT 'custom',
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` char(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `module` (`module`)
+) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workflowlayout` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `module` varchar(30) NOT NULL,
+ `action` varchar(50) NOT NULL,
+ `field` varchar(50) NOT NULL,
+ `order` smallint(5) unsigned NOT NULL,
+ `width` smallint(5) NOT NULL,
+ `position` text NOT NULL,
+ `readonly` enum('0','1') NOT NULL DEFAULT '0',
+ `mobileShow` enum('0','1') NOT NULL DEFAULT '1',
+ `summary` varchar(20) NOT NULL,
+ `defaultValue` text NOT NULL,
+ `layoutRules` varchar(255) NOT NULL,
+ `vision` varchar(10) NOT NULL DEFAULT 'rnd',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `unique` (`module`,`action`,`field`,`vision`),
+ KEY `module` (`module`),
+ KEY `action` (`action`),
+ KEY `order` (`order`)
+) ENGINE=InnoDB AUTO_INCREMENT=138 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workflowlinkdata` (
+ `objectType` varchar(30) NOT NULL,
+ `objectID` mediumint(8) unsigned NOT NULL,
+ `linkedType` varchar(30) NOT NULL,
+ `linkedID` mediumint(8) unsigned NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ UNIQUE KEY `unique` (`objectType`,`objectID`,`linkedType`,`linkedID`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workflowrelation` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `prev` varchar(30) NOT NULL,
+ `next` varchar(30) NOT NULL,
+ `field` varchar(50) NOT NULL,
+ `actions` varchar(20) NOT NULL,
+ `actionCodes` text NOT NULL,
+ `buildin` enum('0','1') NOT NULL DEFAULT '0',
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workflowrelationlayout` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `prev` varchar(30) NOT NULL,
+ `next` varchar(30) NOT NULL,
+ `action` varchar(50) NOT NULL,
+ `field` varchar(50) NOT NULL,
+ `order` smallint(5) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `unique` (`prev`,`next`,`action`,`field`),
+ KEY `prev` (`prev`),
+ KEY `next` (`next`),
+ KEY `action` (`action`),
+ KEY `order` (`order`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workflowreport` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `module` varchar(30) NOT NULL COMMENT 'module name',
+ `name` varchar(100) NOT NULL COMMENT 'report name',
+ `type` enum('pie','line','bar') NOT NULL DEFAULT 'pie' COMMENT 'report type',
+ `countType` enum('sum','count') NOT NULL DEFAULT 'sum' COMMENT 'report count method',
+ `displayType` enum('value','percent') NOT NULL DEFAULT 'value' COMMENT 'report display method',
+ `dimension` varchar(130) NOT NULL COMMENT 'dimension field code of zt_workflowfield',
+ `fields` text NOT NULL COMMENT 'count fileds code of zt_workflowfield,use comma split',
+ `order` smallint(5) unsigned NOT NULL DEFAULT '0',
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workflowrule` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `type` enum('system','regex','func') NOT NULL DEFAULT 'regex',
+ `name` varchar(30) NOT NULL,
+ `rule` text NOT NULL,
+ `createdBy` char(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` char(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `type` (`type`)
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workflowsql` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `module` varchar(30) NOT NULL,
+ `field` varchar(50) NOT NULL,
+ `action` varchar(50) NOT NULL,
+ `sql` text NOT NULL,
+ `vars` text NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `module` (`module`),
+ KEY `field` (`field`),
+ KEY `action` (`action`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_workflowversion` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `module` varchar(30) NOT NULL,
+ `version` varchar(10) NOT NULL,
+ `fields` text NOT NULL,
+ `actions` text NOT NULL,
+ `layouts` text NOT NULL,
+ `sqls` text NOT NULL,
+ `labels` text NOT NULL,
+ `table` text NOT NULL,
+ `datas` text NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `moduleversion` (`module`,`version`),
+ KEY `module` (`module`),
+ KEY `version` (`version`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `zt_zoutput` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `activity` mediumint(8) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `content` mediumtext NOT NULL,
+ `optional` char(20) NOT NULL,
+ `tailorNorm` varchar(255) NOT NULL,
+ `status` varchar(30) NOT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `order` mediumint(8) DEFAULT '0',
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=130 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_dayactions` AS SELECT
+ 1 AS `actions`,
+ 1 AS `day`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_daybugopen` AS SELECT
+ 1 AS `bugopen`,
+ 1 AS `day`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_daybugresolve` AS SELECT
+ 1 AS `bugresolve`,
+ 1 AS `day`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_dayeffort` AS SELECT
+ 1 AS `consumed`,
+ 1 AS `date`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_daystoryclose` AS SELECT
+ 1 AS `storyclose`,
+ 1 AS `day`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_daystoryopen` AS SELECT
+ 1 AS `storyopen`,
+ 1 AS `day`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_daytaskfinish` AS SELECT
+ 1 AS `taskfinish`,
+ 1 AS `day`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_daytaskopen` AS SELECT
+ 1 AS `taskopen`,
+ 1 AS `day`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_dayuserlogin` AS SELECT
+ 1 AS `userlogin`,
+ 1 AS `day`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_executionsummary` AS SELECT
+ 1 AS `execution`,
+ 1 AS `estimate`,
+ 1 AS `consumed`,
+ 1 AS `left`,
+ 1 AS `number`,
+ 1 AS `undone`,
+ 1 AS `totalReal`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_productbugs` AS SELECT
+ 1 AS `product`,
+ 1 AS `bugs`,
+ 1 AS `resolutions`,
+ 1 AS `seriousBugs`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_productstories` AS SELECT
+ 1 AS `product`,
+ 1 AS `stories`,
+ 1 AS `undone`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_projectbugs` AS SELECT
+ 1 AS `execution`,
+ 1 AS `bugs`,
+ 1 AS `resolutions`,
+ 1 AS `seriousBugs`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_projectstories` AS SELECT
+ 1 AS `execution`,
+ 1 AS `stories`,
+ 1 AS `undone`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_projectsummary` AS SELECT
+ 1 AS `project`,
+ 1 AS `estimate`,
+ 1 AS `consumed`,
+ 1 AS `left`,
+ 1 AS `number`,
+ 1 AS `undone`,
+ 1 AS `totalReal`*/;
+SET character_set_client = @saved_cs_client;
+SET @saved_cs_client = @@character_set_client;
+SET character_set_client = utf8;
+/*!50001 CREATE VIEW `ztv_projectteams` AS SELECT
+ 1 AS `execution`,
+ 1 AS `teams`*/;
+SET character_set_client = @saved_cs_client;
+/*!50001 DROP VIEW IF EXISTS `view_datasource_10`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `view_datasource_10` AS select `zt_build`.`id` AS `id`,`zt_build`.`name` AS `name` from `zt_build` where (`zt_build`.`deleted` = '0') */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `view_datasource_11`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `view_datasource_11` AS select `zt_module`.`id` AS `id`,`zt_module`.`name` AS `name` from `zt_module` where (`zt_module`.`deleted` = '0') */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `view_datasource_12`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `view_datasource_12` AS select `zt_productplan`.`id` AS `id`,`zt_productplan`.`title` AS `title` from `zt_productplan` where (`zt_productplan`.`deleted` = '0') */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `view_datasource_4`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `view_datasource_4` AS select `zt_story`.`id` AS `id`,`zt_story`.`title` AS `title` from `zt_story` where (`zt_story`.`deleted` = '0') */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `view_datasource_41`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `view_datasource_41` AS select `zt_case`.`id` AS `id`,`zt_case`.`title` AS `title` from `zt_case` where (`zt_case`.`deleted` = '0') */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `view_datasource_46`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `view_datasource_46` AS select `zt_task`.`id` AS `id`,`zt_task`.`name` AS `name` from `zt_task` where ((`zt_task`.`deleted` = '0') and (`zt_task`.`vision` = 'lite')) */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `view_datasource_5`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `view_datasource_5` AS select `zt_task`.`id` AS `id`,`zt_task`.`name` AS `name` from `zt_task` where ((`zt_task`.`deleted` = '0') and (`zt_task`.`vision` = 'rnd')) */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `view_datasource_6`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `view_datasource_6` AS select `zt_bug`.`id` AS `id`,`zt_bug`.`title` AS `title` from `zt_bug` where (`zt_bug`.`deleted` = '0') */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_dayactions`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_dayactions` AS select count(0) AS `actions`,left(`zt_action`.`date`,10) AS `day` from `zt_action` group by left(`zt_action`.`date`,10) */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_daybugopen`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_daybugopen` AS select count(0) AS `bugopen`,left(`zt_action`.`date`,10) AS `day` from `zt_action` where ((`zt_action`.`objectType` = 'bug') and (`zt_action`.`action` = 'opened')) group by left(`zt_action`.`date`,10) */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_daybugresolve`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_daybugresolve` AS select count(0) AS `bugresolve`,left(`zt_action`.`date`,10) AS `day` from `zt_action` where ((`zt_action`.`objectType` = 'bug') and (`zt_action`.`action` = 'resolved')) group by left(`zt_action`.`date`,10) */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_dayeffort`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_dayeffort` AS select round(sum(`zt_effort`.`consumed`),1) AS `consumed`,`zt_effort`.`date` AS `date` from `zt_effort` group by `zt_effort`.`date` */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_daystoryclose`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_daystoryclose` AS select count(0) AS `storyclose`,left(`zt_action`.`date`,10) AS `day` from `zt_action` where ((`zt_action`.`objectType` = 'story') and (`zt_action`.`action` = 'closed')) group by left(`zt_action`.`date`,10) */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_daystoryopen`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_daystoryopen` AS select count(0) AS `storyopen`,left(`zt_action`.`date`,10) AS `day` from `zt_action` where ((`zt_action`.`objectType` = 'story') and (`zt_action`.`action` = 'opened')) group by left(`zt_action`.`date`,10) */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_daytaskfinish`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_daytaskfinish` AS select count(0) AS `taskfinish`,left(`zt_action`.`date`,10) AS `day` from `zt_action` where ((`zt_action`.`objectType` = 'task') and (`zt_action`.`action` = 'finished')) group by left(`zt_action`.`date`,10) */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_daytaskopen`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_daytaskopen` AS select count(0) AS `taskopen`,left(`zt_action`.`date`,10) AS `day` from `zt_action` where ((`zt_action`.`objectType` = 'task') and (`zt_action`.`action` = 'opened')) group by left(`zt_action`.`date`,10) */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_dayuserlogin`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_dayuserlogin` AS select count(0) AS `userlogin`,left(`zt_action`.`date`,10) AS `day` from `zt_action` where ((`zt_action`.`objectType` = 'user') and (`zt_action`.`action` = 'login')) group by left(`zt_action`.`date`,10) */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_executionsummary`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_executionsummary` AS select `zt_task`.`execution` AS `execution`,sum(if((`zt_task`.`parent` >= '0'),`zt_task`.`estimate`,0)) AS `estimate`,sum(if((`zt_task`.`parent` >= '0'),`zt_task`.`consumed`,0)) AS `consumed`,sum(if(((`zt_task`.`status` <> 'cancel') and (`zt_task`.`status` <> 'closed') and (`zt_task`.`parent` >= '0')),`zt_task`.`left`,0)) AS `left`,count(0) AS `number`,sum(if(((`zt_task`.`status` <> 'done') and (`zt_task`.`status` <> 'closed')),1,0)) AS `undone`,sum((if((`zt_task`.`parent` >= '0'),`zt_task`.`consumed`,0) + if(((`zt_task`.`status` <> 'cancel') and (`zt_task`.`status` <> 'closed') and (`zt_task`.`parent` >= '0')),`zt_task`.`left`,0))) AS `totalReal` from `zt_task` where (`zt_task`.`deleted` = '0') group by `zt_task`.`execution` */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_productbugs`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_productbugs` AS select `zt_bug`.`product` AS `product`,count(0) AS `bugs`,sum(if((`zt_bug`.`resolution` = ''),0,1)) AS `resolutions`,sum(if((`zt_bug`.`severity` <= 2),1,0)) AS `seriousBugs` from `zt_bug` where (`zt_bug`.`deleted` = '0') group by `zt_bug`.`product` */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_productstories`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_productstories` AS select `zt_story`.`product` AS `product`,count('*') AS `stories`,sum(if((`zt_story`.`status` = 'closed'),0,1)) AS `undone` from `zt_story` where (`zt_story`.`deleted` = '0') group by `zt_story`.`product` */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_projectbugs`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_projectbugs` AS select `zt_bug`.`execution` AS `execution`,count(0) AS `bugs`,sum(if((`zt_bug`.`resolution` = ''),0,1)) AS `resolutions`,sum(if((`zt_bug`.`severity` <= 2),1,0)) AS `seriousBugs` from `zt_bug` where (`zt_bug`.`deleted` = '0') group by `zt_bug`.`execution` */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_projectstories`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_projectstories` AS select `t1`.`project` AS `execution`,count('*') AS `stories`,sum(if((`t2`.`status` = 'closed'),0,1)) AS `undone` from ((`zt_projectstory` `t1` left join `zt_story` `t2` on((`t1`.`story` = `t2`.`id`))) left join `zt_project` `t3` on((`t1`.`project` = `t3`.`id`))) where ((`t2`.`deleted` = '0') and (`t3`.`type` in ('sprint','stage'))) group by `t1`.`project` */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_projectsummary`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_projectsummary` AS select `zt_task`.`project` AS `project`,sum(if((`zt_task`.`parent` >= '0'),`zt_task`.`estimate`,0)) AS `estimate`,sum(if((`zt_task`.`parent` >= '0'),`zt_task`.`consumed`,0)) AS `consumed`,sum(if(((`zt_task`.`status` <> 'cancel') and (`zt_task`.`status` <> 'closed') and (`zt_task`.`parent` >= '0')),`zt_task`.`left`,0)) AS `left`,count(0) AS `number`,sum(if(((`zt_task`.`status` <> 'done') and (`zt_task`.`status` <> 'closed')),1,0)) AS `undone`,sum((if((`zt_task`.`parent` >= '0'),`zt_task`.`consumed`,0) + if(((`zt_task`.`status` <> 'cancel') and (`zt_task`.`status` <> 'closed') and (`zt_task`.`parent` >= '0')),`zt_task`.`left`,0))) AS `totalReal` from `zt_task` where (`zt_task`.`deleted` = '0') group by `zt_task`.`project` */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
+/*!50001 DROP VIEW IF EXISTS `ztv_projectteams`*/;
+/*!50001 SET @saved_cs_client = @@character_set_client */;
+/*!50001 SET @saved_cs_results = @@character_set_results */;
+/*!50001 SET @saved_col_connection = @@collation_connection */;
+/*!50001 SET character_set_client = utf8 */;
+/*!50001 SET character_set_results = utf8 */;
+/*!50001 SET collation_connection = utf8_general_ci */;
+/*!50001 CREATE ALGORITHM=UNDEFINED */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
+/*!50001 VIEW `ztv_projectteams` AS select `zt_team`.`root` AS `execution`,count('*') AS `teams` from `zt_team` where (`zt_team`.`type` = 'execution') group by `zt_team`.`root` */;
+/*!50001 SET character_set_client = @saved_cs_client */;
+/*!50001 SET character_set_results = @saved_cs_results */;
+/*!50001 SET collation_connection = @saved_col_connection */;
diff --git a/db/update18.0.beta1.sql b/db/update18.0.beta1.sql
index 6b6fedabbd..bc70663b0f 100644
--- a/db/update18.0.beta1.sql
+++ b/db/update18.0.beta1.sql
@@ -140,3 +140,9 @@ ALTER TABLE `zt_repofiles` ADD `oldPath` varchar(255) DEFAULT '' AFTER `path`;
ALTER TABLE `zt_testresult` ADD `ZTFResult` text NOT NULL;
ALTER TABLE `zt_testresult` ADD `node` int(8) unsigned NOT NULL DEFAULT '0';
+ALTER TABLE `zt_story` ADD COLUMN `twins` varchar(255) NOT NULL AFTER `linkRequirements`;
+ALTER TABLE `zt_productplan` MODIFY COLUMN `branch` varchar(255) NOT NULL DEFAULT '0' AFTER `product`;
+ALTER TABLE `zt_effort` ADD `extra` text COLLATE 'utf8_general_ci' NOT NULL AFTER `end`;
+ALTER TABLE `zt_build` MODIFY COLUMN `branch` varchar(255) NOT NULL DEFAULT '0' AFTER `product`;
+ALTER TABLE `zt_feedback` ADD COLUMN `activatedBy` varchar(30) NOT NULL AFTER `assignedDate`;
+ALTER TABLE `zt_feedback` ADD COLUMN `activatedDate` datetime NOT NULL AFTER `activatedBy`;
diff --git a/db/zentao.sql b/db/zentao.sql
index 8ab0e46dc1..20603c3359 100755
--- a/db/zentao.sql
+++ b/db/zentao.sql
@@ -321,7 +321,7 @@ CREATE TABLE IF NOT EXISTS `zt_build` (
`id` mediumint(8) unsigned NOT NULL auto_increment,
`project` mediumint(8) unsigned NOT NULL,
`product` mediumint(8) unsigned NOT NULL default '0',
- `branch` mediumint(8) unsigned NOT NULL default '0',
+ `branch` varchar(255) NOT NULL DEFAULT '0',
`execution` mediumint(8) unsigned NOT NULL default '0',
`builds` varchar(255) NOT NULL,
`name` char(150) NOT NULL,
@@ -1129,7 +1129,7 @@ CREATE TABLE IF NOT EXISTS `zt_product` (
CREATE TABLE IF NOT EXISTS `zt_productplan` (
`id` mediumint(8) unsigned NOT NULL auto_increment,
`product` mediumint(8) unsigned NOT NULL,
- `branch` mediumint(8) unsigned NOT NULL,
+ `branch` varchar(255) NOT NULL DEFAULT '0',
`parent` mediumint(9) NOT NULL DEFAULT '0',
`title` varchar(90) NOT NULL,
`status` enum('wait','doing','done','closed') NOT NULL default 'wait',
@@ -1480,6 +1480,7 @@ CREATE TABLE IF NOT EXISTS `zt_story` (
`childStories` varchar(255) NOT NULL,
`linkStories` varchar(255) NOT NULL,
`linkRequirements` varchar(255) NOT NULL,
+ `twins` varchar(255) NOT NULL,
`duplicateStory` mediumint(8) unsigned NOT NULL,
`version` smallint(6) NOT NULL default '1',
`feedbackBy` varchar(100) NOT NULL,
@@ -6401,7 +6402,7 @@ CREATE TABLE IF NOT EXISTS `zt_im_message` (
`user` varchar(30) NOT NULL DEFAULT '',
`date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`index` int(11) unsigned NOT NULL DEFAULT 0,
- `type` enum('normal', 'broadcast', 'notify', 'bulletin') NOT NULL DEFAULT 'normal',
+ `type` enum('normal','broadcast','notify','bulletin','botcommand') NOT NULL DEFAULT 'normal',
`content` text NOT NULL DEFAULT '',
`contentType` enum('text', 'plain', 'emotion', 'image', 'file', 'object', 'code') NOT NULL DEFAULT 'text',
`data` text NOT NULL DEFAULT '',
@@ -6555,6 +6556,7 @@ ALTER TABLE `zt_effort` CHANGE `begin` `begin` smallint(4) unsigned zerofill NOT
ALTER TABLE `zt_effort` CHANGE `end` `end` smallint(4) unsigned zerofill NOT NULL AFTER `begin`;
ALTER TABLE `zt_effort` ADD `deleted` enum('0','1') NOT NULL DEFAULT '0' AFTER `end`;
ALTER TABLE `zt_effort` ADD `order` tinyint unsigned NOT NULL DEFAULT '0' AFTER `end`;
+ALTER TABLE `zt_effort` ADD `extra` text COLLATE 'utf8_general_ci' NULL AFTER `end`;
ALTER TABLE `zt_effort` ADD INDEX `execution` (`execution`);
ALTER TABLE `zt_effort` ADD INDEX `objectID` (`objectID`);
ALTER TABLE `zt_effort` ADD INDEX `date` (`date`);
@@ -7129,6 +7131,8 @@ CREATE TABLE IF NOT EXISTS `zt_feedback` (
`editedDate` datetime NOT NULL,
`assignedTo` varchar(255) NOT NULL,
`assignedDate` datetime NOT NULL,
+ `activatedBy` varchar(30) NOT NULL,
+ `activatedDate` datetime NOT NULL,
`feedbackBy` varchar(100) NOT NULL,
`repeatFeedback` mediumint(8) NOT NULL DEFAULT 0,
`mailto` varchar(255) NOT NULL,
@@ -7179,6 +7183,7 @@ CREATE TABLE IF NOT EXISTS `zt_ticket` (
key `product` (`product`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+-- DROP TABLE IF EXISTS `zt_ticketsource`;
CREATE TABLE IF NOT EXISTS `zt_ticketsource` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`ticketId` mediumint(8) unsigned NOT NULL,
@@ -7190,6 +7195,7 @@ CREATE TABLE IF NOT EXISTS `zt_ticketsource` (
key `ticketId` (`ticketId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+-- DROP TABLE IF EXISTS `zt_ticketrelation`;
CREATE TABLE IF NOT EXISTS `zt_ticketrelation` (
`id` mediumint unsigned NOT NULL AUTO_INCREMENT,
`ticketId` mediumint unsigned NOT NULL,
diff --git a/doc/CHANGELOG b/doc/CHANGELOG
index 85b6176b79..d3a26aab39 100644
--- a/doc/CHANGELOG
+++ b/doc/CHANGELOG
@@ -1,3 +1,202 @@
+2022-12-14 18.0.beta2
+完成的需求
+开源版:
+41067 初始化执行节点时可以安装单个服务
+41056 孪生需求编辑的时候一直显示解除的icon
+41054 需求批量关闭页面增加孪生需求标签
+41046 孪生需求关闭时,支持解除孪生关系
+41045 优化按计划导入时提示语
+41044 孪生需求不支持细分
+41043 变更页面支持解除孪生关系
+41042 孪生需求变更页面中同步孪生需求区块调整至影响范围
+41021 计划关联的bug列表字段顺序调整
+41020 计划关联的研发需求列表字段顺序调整
+41018 发布关联的遗留bug列表字段顺序调整
+41016 版本关联的解决的bug列表字段顺序调整
+41015 发布中关联的解决的bug列表字段顺序调整
+41014 版本中关联的研发需求列表字段顺序调整
+41013 发布中关联的研发需求列表字段顺序调整
+41011 产品发布列表部分字段列宽调整
+41008 优化执行节点的远程管理页面
+40984 需求变更时的确认按钮改为ok样式
+40887 创建任务页优化多人任务设置
+40874 未开始的任务支持切换单人或多人任务
+40862 父计划支持编辑分支
+40837 集成版本始终保留自身关联的需求和bug所对应的分支
+40717 测试单可以关联版本对应分支的用例
+40713 发布归属多个分支时,路线图中在不同分支重复展示
+40712 计划列表的”平台/分支“搜索条件关于默认为”包含“。
+40711 项目的需求列表中按计划导入需求时,修改提示语
+40710 发布的列表页中,版本后面展示所属分支
+40709 发布的详情页中,所属分支展示关联版本的分支的合集
+40700 执行中创建版本时,所属分支支持多选
+40691 执行的需求列表中按计划导入需求时,修改提示语
+40649 优化多分支产品创建计划的逻辑
+40648 版本列表中增加所属分支字段
+40646 项目的发布去掉分支字段
+40644 多分支产品修改计划的分支时,同步移除对应分支的bug
+40615 优化创建执行节点的交互
+40524 批量执行自动化用例
+40358 在宿主机列表添加初始化的引导
+40352 优化初始化宿主机的交互
+40350 优化创建宿主机的交互
+40132 需求详情页中用户需求部分交互优化
+39893 编辑执行节点
+39773 主干需求的计划只能选择一个
+39772 详情页面和编辑页面的孪生需求样式优化
+39766 项目页面优化分支关联样式
+39763 执行页面优化分支关联样式
+39699 多分支产品1.5级菜单选所有时,隐藏批量修改模块按钮
+39657 浏览创建镜像进度
+39656 取消下载镜像
+39653 优化下载镜像的交互
+39636 浏览镜像仓库
+39602 孪生需求进行提交评审、撤销评审、撤销变更时,其余孪生需求均同步
+39601 多分支需求批量修改计划时,只能修改至对应分支的计划
+39600 勾选软件需求后进行批量编辑时,过滤掉孪生需求
+39599 孪生需求进行批量评审时,其余孪生需求均同步评审结果
+39575 瀑布项目阶段列表中细分按钮置灰逻辑完善
+39573 孪生需求进行批量关闭时,其余孪生需求均同步关闭
+39570 孪生需求进行批量指派给时,其余孪生需求均同步修改
+39568 研发需求批量修改指派给时过滤已关闭的需求
+39566 有执行节点的宿主机不能删除
+39564 浏览宿主机详情
+39562 需求批量修改阶段时修改提示语
+39558 创建孪生需求时相同分支只能选一次
+39448 编辑和变更孪生需求时,同步需求内容
+39445 孪生需求激活时,状态同步
+39436 孪生需求关闭时,状态同步
+39435 孪生需求指派时,指派人同步
+39424 孪生需求的产品进行切换后,解除孪生关系
+39423 复制需求时不复制孪生关系
+39422 孪生需求的变更页面中“影响范围”展示所有相关需求的影响范围
+39421 修改子计划分支时,只能在父计划的分支内选择
+39416 变更和编辑孪生需求时可以解除孪生关系
+39353 编写更新镜像下载状态的接口
+39302 币种设置中“台元”调整为”台币“
+39269 根据提供的ostype文件表的结构维护操作系统
+39224 初始化宿主机
+39196 编辑和变更孪生需求时增加同步的提示信息
+39177 基于执行节点创建镜像
+39150 宿主机网络连通性检验常用端口
+39148 编写初始化宿主机脚本
+39145 从镜像创建执行节点
+39143 宿主机列表添加初始化按钮
+39142 调整添加和编辑执行节点页面控件
+39141 调整添加和编辑执行节点页面的样式
+39138 调整添加和编辑宿主机页面控件
+39085 下载镜像
+39084 调整添加和编辑宿主机表单样式
+39083 调整测试下二级导航栏
+38958 解除多分支需求的孪生关系后,需求不再同步
+38956 孪生需求评审时,评审的流程及状态同步
+38654 优化创建项目页产品和计划的选择样式
+38653 优化创建执行页产品和计划的选择样式
+38645 执行按计划关联需求时,只关联本执行所关联分支的激活需求
+38486 初始化执行节点
+38482 用例列表添加自动化测试设置
+38459 项目按计划关联需求时,只关联本项目所关联分支的激活需求
+38373 孪生需求详情页中展示其余孪生需求
+38233 创建执行时可关联计划的范围需根据分支过滤
+38232 创建项目时可关联计划的范围需根据分支过滤
+37968 版本多选分支后,关联需求和bug只能关联分支内的
+37966 项目中创建版本时,所属分支支持多选
+37958 产品计划看板中,属于多个分支的计划多次展示
+37957 计划多选分支后,关联需求和bug只能关联分支内的
+37955 创建计划时,所属分支支持多选
+37818 禅道向执行节点发送请求时带上token
+37618 浏览执行节点详情
+37614 查看自动化用例执行结果
+37611 执行自动化用例
+36993 通过NoVNC远程操控执行节点
+36991 实现删除执行节点的功能
+36987 实现重启执行节点的功能
+36986 实现恢复执行节点的功能
+36985 实现暂停执行节点的功能
+36980 实现基于宿主机添加执行节点的功能
+36978 在测试自动化下增加执行节点列表页面
+36965 实现删除宿主机的功能
+36963 实现编辑宿主机的功能
+34485 批量提用例页面自定义表单不选择分支时,所属模块展示“主干”的模块
+34484 批量提bug页面自定义表单不选择分支时,所属模块展示“主干”的模块
+31514 多分支产品维护模块时上级模块要跟随所属产品分支联动
+30874 创建需求时,所属分支可以多选
+企业版:
+40541 上传课程支持断点续传功能
+39945 课程中可以替换需要修正的视频
+39691 课程中可以上传新视频
+39652 视频无法播放时给出提示
+38978 课程上传组件支持批量上传功能
+36843 调整系统备份逻辑不备份学堂数据
+旗舰版:
+40764 我的地盘审批列表过滤掉无数据的对象标签
+39800 调整瀑布项目计划区块的阶段进度计算公式
+39308 对瀑布类型项目进行评审时填报的工时可以记录到日志中
+37519 地盘审批列表可以展示在工作流中配置的审批流
+34166 消息通知增加审批流相关的通知设置
+禅道客户端:
+37925 实现原小喧喧以一对一会话的形式展示
+37930 实现机器人会话消息显示顺序调整
+37960 实现机器人会话消息右键菜单操作
+37959 实现在消息记录窗口中查看小喧喧历史消息
+37948 实现机器人会话在最近会话列表的提醒展示
+38620 实现机器人会话指令机制
+38500 实现内置应用查看服务器基础信息demo指令设计
+38519 实现内置应用查看企业授权信息demo指令设计
+38526 实现内置应用查看人员信息demo指令设计
+38529 实现内置提示音开关demo指令设计
+38438 实现查询指令特殊情况下的默认回复
+39186 实现会话列表中会议中、连接会议图标的干扰
+38688 实现机器人会话自动识错功能
+38426 实现查询任务指令结果以详情形式展示
+38406 实现通过任务指派人过滤任务的指令格式设计
+38646 实现机器人会话指令与禅道协同的机制
+38497 实现机器人会话应用选择
+38432 实现对话机器人查询任务指令的全局设计
+38421 实现查询任务指令结果以列表展示
+38407 实现通过任务优先级过滤任务的指令格式设计
+38409 实现引导帮助指令设计
+39261 实现查询任务显示列表增加快速操作任务的按钮设计
+38959 实现机器人会话操作任务指令与禅道协同的机制
+38961 实现对话机器人操作任务指令的全局设计
+39021 实现对话机器人指令开始禅道任务的设计
+39026 实现对话机器人指令完成禅道任务的设计
+38970 实现对话机器人指令关闭禅道任务的设计
+39180 实现群管理应用中合并群组时可以通过关键字进行搜索
+39181 实现加载会话消息记录的功能
+39187 实现图片查看时工具栏的优化显示
+39182 实现xxb后台用户默认职位角色可以删除操作
+39184 优化图片另存为浮窗交互显示
+39183 删除解散后的讨论组的相关操作
+解决的bug:
+24883 同时打开多个页签情况下出现页签导航和实际功能混乱情况
+29140 wiki编辑章节可以选择自身,保存会出问题
+29143 k8s环境替换正式授权之后频繁出现获取uid失败的提示
+29423 透视表中的部分报表状态显示的是英文
+30275 在反馈的历史记录中点击转的任务会跳转到执行下
+30539 工作流导入日期显示1970-01-01
+30548 用例导入页面输入框与字段错位显示
+30685 通过上传导入图片的文档导出无反应
+30700 创建工单无法选择“所属模块”了
+27979 地盘会议列表中添加会议页面不选择所属项目和执行,会议列表中不显示无所属项目和执行的会议
+28014 禅道客户端聊天中从自己的会话无法查看聊天记录
+27983 用户名大小写不对,提示有问题
+27629 预览txt文件乱码
+27628 喧喧查看大图后复制和另存为看不清楚(Mac用户)
+26559 最小窗口时群成员管理列表样式错误
+28637 已撤回的文件在侧边栏的文件列表中还显示
+28540 某些窗口无法正常弹出
+28512 客户端登录账号后一两分钟就提示账号被禁用,但后台账号状态正常
+28394 客户端导出模板,导入样式显示有问题
+28095 LDAP密码有特殊字符无法登录
+25192 多人快速发送消息时会出现index相同的情况
+14863 使用第三方截图工具截图后,有时在喧喧输入框中粘贴不上
+29037 bug指派无法弹窗
+29029 回复消息最后一层信息没有居左对齐
+28905 聊天发送图片,图片形状编写失真
+28529 macos客户端页面渲染异常
+28297 禅道客户端更新工时弹窗出现遮挡
+
2022-11-16 18.0.beta1
完成的需求
开源版:
diff --git a/framework/api/entry.class.php b/framework/api/entry.class.php
index 33018ca827..0b3f15714b 100644
--- a/framework/api/entry.class.php
+++ b/framework/api/entry.class.php
@@ -98,7 +98,7 @@ class baseEntry
* Get request params.
*
* @param string $key
- * @param string $defaultValue
+ * @param mixed $defaultValue
* @access public
* @return mixed
*/
@@ -112,13 +112,18 @@ class baseEntry
* 设置请求参数
* Set request param.
*
- * @param string $key
- * @param mixed $value
+ * @param string|array $key if is array, set params by its key-value pairs.
+ * @param mixed $value
* @access public
- * @return mixed
+ * @return void
*/
- public function setParam($key, $value)
+ public function setParam($key, $value = null)
{
+ if(is_array($key))
+ {
+ foreach($key as $k => $v) $_GET[$k] = $v;
+ return;
+ }
$_GET[$key] = $value;
}
diff --git a/framework/base/control.class.php b/framework/base/control.class.php
index cc4853fe49..ab6415af38 100644
--- a/framework/base/control.class.php
+++ b/framework/base/control.class.php
@@ -600,7 +600,6 @@ class baseControl
if(!empty($jsExtPath))
{
- $realModulePath = realPath($modulePath);
foreach($jsExtPath as $jsPath)
{
if(empty($jsPath)) continue;
@@ -730,7 +729,7 @@ class baseControl
chdir(dirname($viewFile));
/**
- * 使用extract安定ob方法渲染$viewFile里面的代码。
+ * 使用extract和ob方法渲染$viewFile里面的代码。
* Use extract and ob functions to eval the codes in $viewFile.
*/
extract((array)$this->view);
diff --git a/framework/base/helper.class.php b/framework/base/helper.class.php
index 1089b8d145..acec227494 100644
--- a/framework/base/helper.class.php
+++ b/framework/base/helper.class.php
@@ -87,7 +87,7 @@ class baseHelper
/* 生成url链接的开始部分。Set the begin parts of the link. */
if($config->requestType == 'PATH_INFO') $link = $config->webRoot . $appName;
if($config->requestType != 'PATH_INFO') $link = $config->webRoot . $appName . basename($_SERVER['SCRIPT_NAME']);
- if($config->requestType == 'PATH_INFO2') $link .= '/';
+ if($config->requestType == 'PATH_INFO2') $link = '/';
/**
* #1: RequestType为GET。When the requestType is GET.
@@ -178,7 +178,7 @@ class baseHelper
* Check in only body mode or not.
*
* @access public
- * @return void
+ * @return bool
*/
public static function inOnlyBodyMode()
{
@@ -464,7 +464,7 @@ class baseHelper
$agent = $_SERVER["HTTP_USER_AGENT"];
- /* Chrome should checked before safari.*/
+ /* Chrome should check before safari.*/
if(strpos($agent, 'Firefox') !== false) $browser['name'] = "firefox";
if(strpos($agent, 'Opera') !== false) $browser['name'] = 'opera';
if(strpos($agent, 'Safari') !== false) $browser['name'] = 'safari';
@@ -853,7 +853,7 @@ function a($var)
* Judge the server ip is local or not.
*
* @access public
- * @return void
+ * @return bool
*/
function isLocalIP()
{
@@ -954,33 +954,42 @@ function htmlSpecialString($string, $flags = '', $encoding = 'UTF-8')
return htmlspecialchars($string, $flags, $encoding);
}
-if (!function_exists('array_column'))
+if(!function_exists('array_column'))
{
function array_column(array $input, $columnKey, $indexKey = null)
{
$output = array();
- foreach ($input as $row) {
- $key = $value = null;
+ foreach($input as $row)
+ {
+ $key = $value = null;
$keySet = $valueSet = false;
- if (null !== $indexKey && array_key_exists($indexKey, $row)) {
+ if(null !== $indexKey && array_key_exists($indexKey, $row))
+ {
$keySet = true;
- $key = (string) $row[$indexKey];
+ $key = (string) $row[$indexKey];
}
- if (null === $columnKey) {
+ if(null === $columnKey)
+ {
$valueSet = true;
- $value = $row;
- } elseif (\is_array($row) && \array_key_exists($columnKey, $row)) {
+ $value = $row;
+ }
+ elseif(\is_array($row) && \array_key_exists($columnKey, $row))
+ {
$valueSet = true;
- $value = $row[$columnKey];
+ $value = $row[$columnKey];
}
- if ($valueSet) {
- if ($keySet) {
+ if($valueSet)
+ {
+ if($keySet)
+ {
$output[$key] = $value;
- } else {
+ }
+ else
+ {
$output[] = $value;
}
}
diff --git a/framework/base/model.class.php b/framework/base/model.class.php
index bf4a0136ab..3c734d0071 100644
--- a/framework/base/model.class.php
+++ b/framework/base/model.class.php
@@ -281,8 +281,8 @@ class baseModel
/* 设置扩展的名字和相应的文件。Set extenson name and extension file. */
$moduleExtPath = $this->app->getModuleExtPath($this->appName, $moduleName, $type);
if(!empty($moduleExtPath['site'])) $extensionFile = $moduleExtPath['site'] . 'class/' . $extensionName . '.class.php';
- if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['custom'] . 'class/' . $extensionName . '.class.php';
if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['saas'] . 'class/' . $extensionName . '.class.php';
+ if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['custom'] . 'class/' . $extensionName . '.class.php';
if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['vision'] . 'class/' . $extensionName . '.class.php';
if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['xuan'] . 'class/' . $extensionName . '.class.php';
if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['common'] . 'class/' . $extensionName . '.class.php';
diff --git a/framework/base/router.class.php b/framework/base/router.class.php
index de909ac83a..e1a87cf014 100644
--- a/framework/base/router.class.php
+++ b/framework/base/router.class.php
@@ -1719,7 +1719,7 @@ class baseRouter
}
/* 1. 如果extensionLevel == 2,且扩展文件存在,返回该站点扩展文件。 If extensionLevel == 2 and site extensionFile exists, return it. */
- if($this->config->framework->extensionLevel == 2 and !empty( $moduleExtPaths['site']))
+ if($this->config->framework->extensionLevel == 2 and !empty($moduleExtPaths['site']))
{
$this->extActionFile = $moduleExtPaths['site'] . $this->methodName . '.php';
if(file_exists($this->extActionFile)) return true;
@@ -1757,7 +1757,7 @@ class baseRouter
if(empty($moduleExtPaths)) return false;
/* 如果extensionLevel == 2,且扩展文件存在,返回该站点扩展文件。If extensionLevel == 2 and site extensionFile exists, return it. */
- if($this->config->framework->extensionLevel == 2 and !empty( $moduleExtPaths['site']))
+ if($this->config->framework->extensionLevel == 2 and !empty($moduleExtPaths['site']))
{
$locateFile = $moduleExtPaths['site'] . $this->methodName . '.302';
if(file_exists($locateFile)) $this->sendAPI($locateFile);
@@ -2497,9 +2497,8 @@ class baseRouter
*
* @param string $moduleName module name
* @param string $appName app name
- * @param bool $exitIfNone exit or not
* @access public
- * @return object|bool the config object or false.
+ * @return void
*/
public function loadModuleConfig($moduleName, $appName = '')
{
@@ -2525,7 +2524,7 @@ class baseRouter
if(!empty($extConfigPath['saas'])) $commonExtConfigFiles = array_merge($commonExtConfigFiles, helper::ls($extConfigPath['saas'], '.php'));
if(!empty($extConfigPath['custom'])) $commonExtConfigFiles = array_merge($commonExtConfigFiles, helper::ls($extConfigPath['custom'], '.php'));
}
- if($config->framework->extensionLevel == 2 and !empty($extConfigPath['site'])) $siteExtConfigFiles = helper::ls($extConfigPath['site'], '.php');
+ if($config->framework->extensionLevel == 2 and !empty($extConfigPath['site'])) $siteExtConfigFiles = helper::ls($extConfigPath['site'], '.php');
$extConfigFiles = array_merge($commonExtConfigFiles, $siteExtConfigFiles);
/* 将主配置文件和扩展配置文件合并在一起。Put the main config file and extension config files together. */
@@ -2690,12 +2689,12 @@ class baseRouter
if($this->config->framework->extensionLevel >= 1)
{
if(!empty($extLangPath['common'])) $commonExtLangFiles = helper::ls($extLangPath['common'] . $this->clientLang, '.php');
- if(!empty($extLangPath['xuan'])) $commonExtLangFiles = array_merge($commonExtLangFiles, helper::ls($extLangPath['xuan'] . $this->clientLang, '.php'));
+ if(!empty($extLangPath['xuan'])) $commonExtLangFiles = array_merge($commonExtLangFiles, helper::ls($extLangPath['xuan'] . $this->clientLang, '.php'));
if(!empty($extLangPath['vision'])) $commonExtLangFiles = array_merge($commonExtLangFiles, helper::ls($extLangPath['vision'] . $this->clientLang, '.php'));
if(!empty($extLangPath['custom'])) $commonExtLangFiles = array_merge($commonExtLangFiles, helper::ls($extLangPath['custom'] . $this->clientLang, '.php'));
if(!empty($extLangPath['saas'])) $commonExtLangFiles = array_merge($commonExtLangFiles, helper::ls($extLangPath['saas'] . $this->clientLang, '.php'));
}
- if($this->config->framework->extensionLevel == 2 and !empty($extLangPath['site'])) $siteExtLangFiles = helper::ls($extLangPath['site'] . $this->clientLang, '.php');
+ if($this->config->framework->extensionLevel == 2 and !empty($extLangPath['site'])) $siteExtLangFiles = helper::ls($extLangPath['site'] . $this->clientLang, '.php');
$extLangFiles = array_merge($commonExtLangFiles, $siteExtLangFiles);
}
diff --git a/framework/router.class.php b/framework/router.class.php
index dac06b4ac9..9be28106e7 100755
--- a/framework/router.class.php
+++ b/framework/router.class.php
@@ -315,9 +315,8 @@ class router extends baseRouter
*
* @param string $moduleName module name
* @param string $appName app name
- * @param bool $exitIfNone exit or not
* @access public
- * @return object|bool the config object or false.
+ * @return void
*/
public function loadModuleConfig($moduleName, $appName = '')
{
diff --git a/lib/pager/pager.class.php b/lib/pager/pager.class.php
index 0d0b016fa9..a3cfd2f4ba 100644
--- a/lib/pager/pager.class.php
+++ b/lib/pager/pager.class.php
@@ -130,7 +130,11 @@ class pager extends basePager
}
}
- echo "
";
+ global $app, $lang;
+ $appendApp = '';
+ $moduleName = $this->moduleName;
+ if(isset($lang->navGroup->{$moduleName}) and $lang->navGroup->{$moduleName} != $app->tab) $appendApp = "#app={$app->tab}";
+ echo "";
}
}
else
diff --git a/module/action/config.php b/module/action/config.php
index 62052e47b1..0d470d4635 100755
--- a/module/action/config.php
+++ b/module/action/config.php
@@ -63,7 +63,7 @@ $config->action->majorList['project'] = array('opened', 'edited');
$config->action->majorList['execution'] = array('opened', 'edited');
$config->action->needGetProjectType = 'build,task,bug,case,testcase,caselib,testtask,testsuite,testreport,doc,issue,release,risk,design,opportunity,trainplan,gapanalysis,researchplan,researchreport,';
-$config->action->needGetRelateField = ',story,productplan,release,task,build,bug,testcase,case,testtask,testreport,doc,doclib,issue,risk,opportunity,trainplan,gapanalysis,team,whitelist,researchplan,researchreport,meeting,kanbanlane,kanbancolumn,module,';
+$config->action->needGetRelateField = ',story,productplan,release,task,build,bug,testcase,case,testtask,testreport,doc,doclib,issue,risk,opportunity,trainplan,gapanalysis,team,whitelist,researchplan,researchreport,meeting,kanbanlane,kanbancolumn,module,review,';
$config->action->noLinkModules = ',doclib,module,webhook,gitlab,gitea,gogs,sonarqube,pipeline,jenkins,kanban,kanbanspace,kanbancolumn,kanbanlane,kanbanregion,kanbancard,execution,project,traincategory,apistruct,program,product,user,entry,repo,';
$config->action->ignoreObjectType4Dynamic = 'kanbanregion,kanbanlane,kanbancolumn';
diff --git a/module/action/lang/de.php b/module/action/lang/de.php
index ae647ebecc..161859e296 100644
--- a/module/action/lang/de.php
+++ b/module/action/lang/de.php
@@ -231,6 +231,7 @@ $lang->action->desc->suspend = '$date, the ZenAgent Node is suspend
$lang->action->desc->resume = '$date, the ZenAgent Node is resumed by $actor .' . "\n";
$lang->action->desc->reboot = '$date, the ZenAgent Node is reboot by $actor .' . "\n";
$lang->action->desc->destroy = '$date, the ZenAgent Node is destroyed by $actor .' . "\n";
+$lang->action->desc->relieved = '$date, relieved by $actor .' . "\n";
$lang->action->desc->switchtolight = '$date, Switch from ALM mode to light mode by '. $lang->admin->system .' .' . "\n";
$lang->action->desc->getvnc = '$date, Remote control $extra by $actor .' . "\n";
$lang->action->desc->unlinkproduct = '$date, the project is disassociated from the $extra, synchronization disassociates the ' . $lang->executionCommon . 's of the project from the $extra.' . "\n";
@@ -408,6 +409,8 @@ $lang->action->label->suspend = 'suspended';
$lang->action->label->resume = 'resumed';
$lang->action->label->reboot = 'reboot';
$lang->action->label->destroy = 'destroyed';
+$lang->action->label->synctwins = 'synchronized changes';
+$lang->action->label->relieved = 'relieved';
$lang->action->label->switchtolight = 'switch from ALM mode to light mode';
$lang->action->label->linkedrepo = 'Linked Code Repo';
$lang->action->label->unlinkedrepo = 'Unlinked Code Repo';
diff --git a/module/action/lang/en.php b/module/action/lang/en.php
index 28d549d44e..40c7a65264 100755
--- a/module/action/lang/en.php
+++ b/module/action/lang/en.php
@@ -231,6 +231,7 @@ $lang->action->desc->suspend = '$date, the ZenAgent Node is suspend
$lang->action->desc->resume = '$date, the ZenAgent Node is resumed by $actor .' . "\n";
$lang->action->desc->reboot = '$date, the ZenAgent Node is reboot by $actor .' . "\n";
$lang->action->desc->destroy = '$date, the ZenAgent Node is destroyed by $actor .' . "\n";
+$lang->action->desc->relieved = '$date, relieved by $actor .' . "\n";
$lang->action->desc->switchtolight = '$date, Switch from ALM mode to light mode by '. $lang->admin->system .' .' . "\n";
$lang->action->desc->getvnc = '$date, Remote control $extra by $actor .' . "\n";
$lang->action->desc->unlinkproduct = '$date, the project is disassociated from the $extra, synchronization disassociates the ' . $lang->executionCommon . 's of the project from the $extra.' . "\n";
@@ -408,6 +409,8 @@ $lang->action->label->suspend = 'suspended';
$lang->action->label->resume = 'resumed';
$lang->action->label->reboot = 'reboot';
$lang->action->label->destroy = 'destroyed';
+$lang->action->label->synctwins = 'synchronized changes';
+$lang->action->label->relieved = 'relieved';
$lang->action->label->switchtolight = 'switch from ALM mode to light mode';
$lang->action->label->linkedrepo = 'Linked Code Repo';
$lang->action->label->unlinkedrepo = 'Unlinked Code Repo';
diff --git a/module/action/lang/fr.php b/module/action/lang/fr.php
index cc23b87958..3d55cab314 100644
--- a/module/action/lang/fr.php
+++ b/module/action/lang/fr.php
@@ -231,6 +231,7 @@ $lang->action->desc->suspend = '$date, the ZenAgent Node is suspend
$lang->action->desc->resume = '$date, the ZenAgent Node is resumed by $actor .' . "\n";
$lang->action->desc->reboot = '$date, the ZenAgent Node is reboot by $actor .' . "\n";
$lang->action->desc->destroy = '$date, the ZenAgent Node is destroyed by $actor .' . "\n";
+$lang->action->desc->relieved = '$date, relieved by $actor .' . "\n";
$lang->action->desc->switchtolight = '$date, Switch from ALM mode to light mode by '. $lang->admin->system .' .' . "\n";
$lang->action->desc->getvnc = '$date, Remote control $extra by $actor .' . "\n";
$lang->action->desc->unlinkproduct = '$date, the project is disassociated from the $extra, synchronization disassociates the ' . $lang->executionCommon . 's of the project from the $extra.' . "\n";
@@ -408,6 +409,8 @@ $lang->action->label->suspend = 'suspended';
$lang->action->label->resume = 'resumed';
$lang->action->label->reboot = 'reboot';
$lang->action->label->destroy = 'destroyed';
+$lang->action->label->synctwins = 'synchronized changes';
+$lang->action->label->relieved = 'relieved';
$lang->action->label->switchtolight = 'switch from ALM mode to light mode';
$lang->action->label->linkedrepo = 'Linked Code Repo';
$lang->action->label->unlinkedrepo = 'Unlinked Code Repo';
diff --git a/module/action/lang/vi.php b/module/action/lang/vi.php
index 698e660763..a49d373b11 100644
--- a/module/action/lang/vi.php
+++ b/module/action/lang/vi.php
@@ -318,6 +318,7 @@ $lang->action->label->suspend = 'suspended';
$lang->action->label->resume = 'resumed';
$lang->action->label->reboot = 'reboot';
$lang->action->label->destroy = 'destroyed';
+$lang->action->label->synctwins = 'synchronized changes';
$lang->action->label->linkedrepo = 'Linked Code Repo';
$lang->action->label->unlinkedrepo = 'Unlinked Code Repo';
diff --git a/module/action/lang/zh-cn.php b/module/action/lang/zh-cn.php
index af5c68dd0c..f3730d43a2 100755
--- a/module/action/lang/zh-cn.php
+++ b/module/action/lang/zh-cn.php
@@ -231,6 +231,7 @@ $lang->action->desc->suspend = '$date, 由 $actor
$lang->action->desc->resume = '$date, 由 $actor 恢复。' . "\n";
$lang->action->desc->reboot = '$date, 由 $actor 重启。' . "\n";
$lang->action->desc->destroy = '$date, 由 $actor 销毁。' . "\n";
+$lang->action->desc->relieved = '$date, 由 $actor 解除孪生需求关系。' . "\n";
$lang->action->desc->switchtolight = '$date, 由于 '. $lang->admin->system .' 从全生命周期管理模式切换为轻量管理模式,项目访问控制由项目集内公开调整为私有。' . "\n";
$lang->action->desc->getvnc = '$date, $actor 对执行节点 $extra 进行了远程操控。' . "\n";
$lang->action->desc->unlinkproduct = '$date, 系统判断由于' . $lang->executionCommon . '所属项目与$extra取消关联,同步将' . $lang->executionCommon . '与$extra取消关联。' . "\n";
@@ -409,6 +410,8 @@ $lang->action->label->resume = '恢复了';
$lang->action->label->reboot = '重启了';
$lang->action->label->destroy = '销毁了';
$lang->action->label->getvnc = '远程操控';
+$lang->action->label->synctwins = '同步修改了';
+$lang->action->label->relieved = '解除了';
$lang->action->label->switchtolight = '从全生命周期管理模式切换为轻量管理模式';
$lang->action->label->linkedrepo = '关联代码库到';
$lang->action->label->unlinkedrepo = '取消了项目与代码库的关联';
diff --git a/module/action/model.php b/module/action/model.php
index 15eedaaedd..c9b0829060 100755
--- a/module/action/model.php
+++ b/module/action/model.php
@@ -50,7 +50,7 @@ class actionModel extends model
$action->extra = $extra;
if(!defined('IN_UPGRADE')) $action->vision = $this->config->vision;
- if($objectType == 'story' and strpos(',reviewpassed,reviewrejected,reviewclarified,reviewreverted,', ",$actionType,") !== false) $action->actor = $this->lang->action->system;
+ if($objectType == 'story' and strpos(',reviewpassed,reviewrejected,reviewclarified,reviewreverted,synctwins,', ",$actionType,") !== false) $action->actor = $this->lang->action->system;
/* Use purifier to process comment. Fix bug #2683. */
$action->comment = fixer::stripDataTags($comment);
@@ -289,6 +289,15 @@ class actionModel extends model
if(strpos(',deleted,', ",$actionType,") !== false) $module = $this->dao->select('*')->from(TABLE_MODULE)->where('id')->eq($objectID)->fetch();
if(!empty($module) and $module->type == 'story') $record['product'] = $module->root;
break;
+ case 'review':
+ $result = $this->dao->select('*')->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch();
+ if($result)
+ {
+ $products = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($result->project)->fetchPairs('product');
+ $record['product'] = join(',', array_keys($products));
+ $record['project'] = zget($result, 'project', 0);
+ }
+ break;
default:
$result = $this->dao->select('*')->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch();
$record['product'] = zget($result, 'product', '0');
@@ -964,6 +973,17 @@ class actionModel extends model
}
}
+ if($action->objectType == 'story' and $action->action == 'synctwins')
+ {
+ if(!empty($extra) and strpos($extra, '|') !== false)
+ {
+ list($operate, $storyID) = explode('|', $extra);
+ $desc['operate'] = $this->lang->$objectType->{$desc['operate']};
+ $link = common::hasPriv('story', 'view') ? html::a(helper::createLink('story', 'view', "storyID=$storyID"), "#$storyID ") : "#$storyID";
+ $actionDesc = str_replace(array('$extra', '$operate'), array($link, $desc['operate'][$operate]), $desc['main']);
+ }
+ }
+
if($action->objectType == 'module' and strpos(',created,moved,', $action->action) !== false)
{
$moduleNames = $this->loadModel('tree')->getOptionMenu($action->objectID, 'story', 0, 'all', '');
@@ -1834,6 +1854,11 @@ class actionModel extends model
}
}
}
+ elseif($action->objectType == 'release')
+ {
+ $release = $this->dao->select('*')->from(TABLE_RELEASE)->where('id')->eq($action->objectID)->fetch();
+ if($release->shadow) $this->dao->update(TABLE_BUILD)->set('deleted')->eq(0)->where('id')->eq($release->shadow)->exec();
+ }
/* Update deleted field in object table. */
$table = $this->config->objectTables[$action->objectType];
diff --git a/module/block/control.php b/module/block/control.php
index d3483b2d07..55475bd481 100755
--- a/module/block/control.php
+++ b/module/block/control.php
@@ -1951,6 +1951,11 @@ class block extends control
$hasViewPriv['review'] = true;
$count['review'] = count($reviews);
$this->view->reviews = $reviews;
+ if($this->config->edition == 'max')
+ {
+ $this->app->loadLang('approval');
+ $this->view->flows = $this->dao->select('module,name')->from(TABLE_WORKFLOW)->where('buildin')->eq(0)->fetchPairs('module', 'name');
+ }
}
$this->view->selfCall = $this->selfCall;
diff --git a/module/block/view/reviewblock.html.php b/module/block/view/reviewblock.html.php
index b379ef8713..cd32e23dfc 100644
--- a/module/block/view/reviewblock.html.php
+++ b/module/block/view/reviewblock.html.php
@@ -29,13 +29,21 @@
type;
- if($type == 'project') $type = 'review';
+ if($type == 'projectreview') $type = 'review';
- $typeName = $lang->$type->common;
+ $typeName = '';
+ if(isset($lang->{$review->type}->common)) $typeName = $lang->{$review->type}->common;
if($type == 'story') $typeName = $lang->my->auditMenu->audit->story;
+ if($review->type == 'projectreview') $typeName = $lang->project->common;
+ if(isset($flows[$review->type])) $typeName = $flows[$review->type];
- $statusList = $lang->$type->statusList;
+ $statusList = array();
+ if(isset($lang->$type->statusList)) $statusList = $lang->$type->statusList;
if($type == 'attend') $statusList = $lang->attend->reviewStatusList;
+ if(!in_array($type, array('story', 'testcase', 'feedback', 'review')) and strpos(",{$config->my->oaObjectType},", ",$type,") === false)
+ {
+ $statusList = $lang->approval->nodeList;
+ }
?>
id?>
@@ -82,6 +90,10 @@
{
common::printLink($module, 'view', $params, $reviewIcon, '', "class='btn' data-toggle='modal' title='{$lang->review->common}'", true, true);
}
+ elseif(!in_array($module, array('story', 'testcase', 'feedback')))
+ {
+ common::printLink($module, 'approvalreview', $params, $reviewIcon, '', "class='btn' data-toggle='modal' title='{$lang->review->common}'", true, true);
+ }
else
{
common::printLink($module, $method, $params, $reviewIcon, '', "class='btn iframe' title='{$lang->review->common}'", true, true);
diff --git a/module/block/view/waterfallganttblock.html.php b/module/block/view/waterfallganttblock.html.php
index 68b5c3132e..a63f49f165 100644
--- a/module/block/view/waterfallganttblock.html.php
+++ b/module/block/view/waterfallganttblock.html.php
@@ -86,12 +86,12 @@
minTimeGap = Math.min(minTimeGap, endDatetime - startDatetime);
item.tasks = [];
item.completeTasks = [];
- item.progress = 0;
+ item.progress = Number.parseFloat(item.taskProgress.replace('%', ''), 10);
plans.push(item);
}
else if(item.type === 'task')
{
- item.progress = Number.parseInt(item.taskProgress.replace('%', ''), 10);
+ item.progress = Number.parseFloat(item.taskProgress.replace('%', ''), 10);
tasks.push(item);
}
});
@@ -105,7 +105,6 @@
plan = plansMap[plan.parent];
if(typeof(plan) != 'object') return;
}
- plan.progress += task.progress;
if(task.progress === 100) plan.completeTasks.push(task);
plan.tasks.push(task);
});
@@ -122,7 +121,6 @@
/* Update gantt plans and bars */
$.each(plans, function(index, plan)
{
- plan.progress = !plan.tasks.length ? 0 : plan.progress / plan.tasks.length;
var $plan = $('
');
$plan.append('' + plan.text + '
');
$plans.append($plan);
diff --git a/module/branch/control.php b/module/branch/control.php
index 832bd4b0d3..b70b8a9c88 100644
--- a/module/branch/control.php
+++ b/module/branch/control.php
@@ -257,18 +257,21 @@ class branch extends control
*
* @param int $productID
* @param int $oldBranch
- * @param string $param
+ * @param string $browseType
* @param int $projectID
* @param bool $withMainBranch
+ * @param string $isTwins
+ * @param string $fieldID
+ * @param string $multiple
* @access public
* @return void
*/
- public function ajaxGetBranches($productID, $oldBranch = 0, $param = 'all', $projectID = 0, $withMainBranch = true)
+ public function ajaxGetBranches($productID, $oldBranch = 0, $browseType = 'all', $projectID = 0, $withMainBranch = true, $isTwins = 'no', $fieldID = '0', $multiple = '')
{
$product = $this->loadModel('product')->getById($productID);
if(empty($product) or $product->type == 'normal') return;
- $branches = $this->loadModel('branch')->getList($productID, $projectID, $param, 'order', null, $withMainBranch);
+ $branches = $this->loadModel('branch')->getList($productID, $projectID, $browseType, 'order', null, $withMainBranch);
$branchTagOption = array();
foreach($branches as $branchInfo)
{
@@ -280,7 +283,10 @@ class branch extends control
$branchTagOption[$oldBranch] = $oldBranch == BRANCH_MAIN ? $branch : ($branch->name . ($branch->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : ''));
}
- return print(html::select('branch', $branchTagOption, $oldBranch, "class='form-control' onchange='loadBranch(this)' data-last='{$oldBranch}'"));
+ $name = $multiple == 'multiple' ? 'branch[]' : 'branch';
+
+ if($isTwins == 'yes') return print(html::select("branches[$fieldID]", $branchTagOption, $oldBranch, "onchange='loadBranchRelation(this.value, $fieldID);' class='form-control chosen control-branch'"));
+ return print(html::select($name, $branchTagOption, $oldBranch, "class='form-control' $multiple onchange='loadBranch(this)' data-last='{$oldBranch}'"));
}
/**
diff --git a/module/bug/control.php b/module/bug/control.php
index 6fa5b76068..0c85721a2d 100755
--- a/module/bug/control.php
+++ b/module/bug/control.php
@@ -488,7 +488,7 @@ class bug extends control
$steps = $this->lang->bug->tplStep . $this->lang->bug->tplResult . $this->lang->bug->tplExpect;
$os = '';
$browser = '';
- $assignedTo = '';
+ $assignedTo = isset($currentProduct->QD) ? $currentProduct->QD : '';
$deadline = '';
$mailto = '';
$keywords = '';
@@ -1802,7 +1802,7 @@ class bug extends control
}
else
{
- return print(js::closeModal('parent.parent'));
+ return print(js::closeModal('parent.parent', 'this', "typeof(parent.parent.setTitleWidth) == 'function' ? parent.parent.setTitleWidth() : parent.parent.location.reload()"));
}
}
if(defined('RUN_MODE') && RUN_MODE == 'api')
@@ -1989,7 +1989,7 @@ class bug extends control
}
else
{
- return print(js::closeModal('parent.parent', 'this', "function(){parent.parent.location.reload();}"));
+ return print(js::closeModal('parent.parent', 'this', "typeof(parent.parent.setTitleWidth) == 'function' ? parent.parent.setTitleWidth() : parent.parent.location.reload()"));
}
}
if(defined('RUN_MODE') && RUN_MODE == 'api')
@@ -2351,21 +2351,23 @@ class bug extends control
$this->config->bug->datatable->fieldList['module']['dataSource']['method'] = 'getAllModulePairs';
$this->config->bug->datatable->fieldList['module']['dataSource']['params'] = 'bug';
- $this->config->bug->datatable->fieldList['project']['dataSource'] = array('module' => 'project', 'method' => 'getPairsByIdList', 'params' => $executionID);
- $this->config->bug->datatable->fieldList['execution']['dataSource'] = array('module' => 'execution', 'method' => 'getPairs', 'params' => $executionID);
+ $object = $this->dao->findById($executionID)->from(TABLE_EXECUTION)->fetch();
+ $projectID = $object->type == 'project' ? $object->id : $object->parent;
+ $this->config->bug->datatable->fieldList['project']['dataSource'] = array('module' => 'project', 'method' => 'getPairsByIdList', 'params' => $projectID);
+ $this->config->bug->datatable->fieldList['execution']['dataSource'] = array('module' => 'execution', 'method' => 'getPairs', 'params' => $projectID);
}
$this->transfer->export('bug');
$this->fetch('file', 'export2' . $_POST['fileType'], $_POST);
}
$product = $this->loadModel('product')->getByID($productID);
- if($product->type == 'normal') $this->config->bug->exportFields = str_replace('branch,', '', $this->config->bug->exportFields);
+ if(isset($product->type) and $product->type == 'normal') $this->config->bug->exportFields = str_replace('branch,', '', $this->config->bug->exportFields);
if($this->app->tab == 'project' or $this->app->tab == 'execution')
{
$execution = $this->loadModel('execution')->getByID($executionID);
if(empty($execution->multiple)) $this->config->bug->exportFields = str_replace('execution,', '', $this->config->bug->exportFields);
- if($product->shadow) $this->config->bug->exportFields = str_replace('product,', '', $this->config->bug->exportFields);
+ if(!empty($product->shadow)) $this->config->bug->exportFields = str_replace('product,', '', $this->config->bug->exportFields);
}
$fileName = $this->lang->bug->common;
@@ -2505,9 +2507,9 @@ class bug extends control
/**
* Ajax get released builds.
- *
- * @param int $productID
- * @param int|string $branch
+ *
+ * @param int $productID
+ * @param int|string $branch
* @access public
* @return string
*/
diff --git a/module/bug/css/create.css b/module/bug/css/create.css
index 746ea7a8b8..f7b68c49fb 100644
--- a/module/bug/css/create.css
+++ b/module/bug/css/create.css
@@ -57,4 +57,4 @@ html[lang='de'] #keywordsAddonLabel, .task {width: 73px;}
#openedBuildLabel {display: inline; position: relative; width: 50%; left: 14%;}
#bugTypeInputGroup > .table-col {width: 40% !important;}
-.pri-text > .label-pri {padding: 1px 5px;}
+.pri-text > .label-pri {padding: 2px 6px;}
diff --git a/module/bug/js/browse.js b/module/bug/js/browse.js
index 0cc3ce246d..5c32f9e35b 100644
--- a/module/bug/js/browse.js
+++ b/module/bug/js/browse.js
@@ -1,6 +1,6 @@
$(function()
{
- if($('#bugList thead th.c-title').width() < 150) $('#bugList thead th.c-title').width(150);
+ setTitleWidth();
/* The display of the adjusting sidebarHeader is synchronized with the sidebar. */
$(".sidebar-toggle").click(function()
diff --git a/module/bug/js/common.js b/module/bug/js/common.js
index 0c74f79a6f..81920153b2 100644
--- a/module/bug/js/common.js
+++ b/module/bug/js/common.js
@@ -24,9 +24,16 @@ if($('#openedBuild').length || $('#resolvedBuild').length || $('[name^=openedBui
{
$.get(createLink('bug', 'ajaxGetReleasedBuilds', 'productID=' + productID), function(data){releasedBuilds = data;}, 'json');
- $('#openedBuild, #resolvedBuild, [name^=openedBuilds]').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'});
+ $('#openedBuild, #resolvedBuild, [name^=openedBuilds]').picker({optionRender: markReleasedBuilds});
}
+/**
+ * Mark released builds.
+ *
+ * @param object $option
+ * @access public
+ * @return void
+ */
function markReleasedBuilds($option)
{
var build = $option.attr('data-value');
@@ -35,7 +42,7 @@ function markReleasedBuilds($option)
if(!$option.find('.label-released').length)
{
var optionText = $option.find('.picker-option-text').html();
- $option.find('.picker-option-text').replaceWith("" + releasedBuild + " " + optionText + "
");
+ $option.find('.picker-option-text').replaceWith("" + optionText + " " + released + "
");
}
}
}
@@ -173,7 +180,7 @@ function loadAllBuilds(object)
{
oldResolvedBuild = $('#resolvedBuild').val() ? $('#resolvedBuild').val() : 0;
link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=resolvedBuild&build=' + oldResolvedBuild + '&branch=0&index=0&type=all');
- $('#resolvedBuildBox').load(link, function(){$(this).find('select').chosen()});
+ $('#resolvedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})});
}
else
{
@@ -212,7 +219,7 @@ function loadAllExecutionBuilds(executionID, productID, buildBox)
$('#pickerDropMenu-pk_openedBuild').remove();
$('#openedBuild').next('.picker').remove();
notice();
- $("#openedBuild").picker({optionRender: markReleasedBuilds, dropWidth: 'auto'});
+ $("#openedBuild").picker({optionRender: markReleasedBuilds});
})
}
if(page == 'edit')
@@ -220,7 +227,7 @@ function loadAllExecutionBuilds(executionID, productID, buildBox)
if(buildBox == 'openedBuildBox')
{
link = createLink('build', 'ajaxGetExecutionBuilds', 'executionID=' + executionID + '&productID=' + productID + '&varName=openedBuild&build=' + oldOpenedBuild + '&branch=' + branch + '&index=0&needCreate=true&type=all');
- $('#openedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})});
+ $('#openedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds})});
}
if(buildBox == 'resolvedBuildBox')
{
@@ -255,7 +262,7 @@ function loadAllProductBuilds(productID, buildBox)
$('#pickerDropMenu-pk_openedBuild').remove();
$('#openedBuild').next('.picker').remove();
notice();
- $("#openedBuild").picker({optionRender: markReleasedBuilds, dropWidth: 'auto'});
+ $("#openedBuild").picker({optionRender: markReleasedBuilds});
})
}
if(page == 'edit')
@@ -263,12 +270,17 @@ function loadAllProductBuilds(productID, buildBox)
if(buildBox == 'openedBuildBox')
{
link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=openedBuild&build=' + oldOpenedBuild + '&branch=' + branch + '&index=0&type=all');
- $('#openedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})});
+ $('#openedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds})});
}
if(buildBox == 'resolvedBuildBox')
{
link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=resolvedBuild&build=' + oldResolvedBuild + '&branch=' + branch + '&index=0&type=all');
- $('#resolvedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})});
+ $('#resolvedBuildBox').load(link, function()
+ {
+ $(this).find('select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})
+ var $pkResolvedBuild = $('#pk_resolvedBuild-search');
+ $pkResolvedBuild.closest('.picker').css('width', $pkResolvedBuild.closest('td').width());
+ });
}
}
}
@@ -474,12 +486,12 @@ function loadProductBuilds(productID)
$('#pickerDropMenu-pk_openedBuild').remove();
$('#openedBuild').next('.picker').remove();
notice();
- $("#openedBuild").picker({optionRender: markReleasedBuilds, dropWidth: 'auto'});
+ $("#openedBuild").picker({optionRender: markReleasedBuilds});
})
}
else
{
- $('#openedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})});
+ $('#openedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds})});
link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=resolvedBuild&build=' + oldResolvedBuild + '&branch=' + branch);
$('#resolvedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})});
}
@@ -632,13 +644,13 @@ function loadProjectBuilds(projectID)
$('#pickerDropMenu-pk_openedBuild').remove();
$('#openedBuild').next('.picker').remove();
notice();
- $("#openedBuild").picker({optionRender: markReleasedBuilds, dropWidth: 'auto'});
+ $("#openedBuild").picker({optionRender: markReleasedBuilds});
})
}
else
{
var link = createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=openedBuild&build=' + oldOpenedBuild + '&branch=' + branch);
- $('#openedBuildBox').load(link, function(){$(this).find('select').val(oldOpenedBuild).picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})});
+ $('#openedBuildBox').load(link, function(){$(this).find('select').val(oldOpenedBuild).picker({optionRender: markReleasedBuilds})});
var oldResolvedBuild = $('#resolvedBuild').val() ? $('#resolvedBuild').val() : 0;
var link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=resolvedBuild&build=' + oldResolvedBuild + '&branch=' + branch);
@@ -677,13 +689,13 @@ function loadExecutionBuilds(executionID, num)
$('#pickerDropMenu-pk_openedBuild').remove();
$('#openedBuild').next('.picker').remove();
notice();
- $("#openedBuild").picker({optionRender: markReleasedBuilds, dropWidth: 'auto'});
+ $("#openedBuild").picker({optionRender: markReleasedBuilds});
})
}
else
{
link = createLink('build', 'ajaxGetExecutionBuilds', 'executionID=' + executionID + '&productID=' + productID + '&varName=openedBuild&build=' + oldOpenedBuild + '&branch=' + branch + '&index=0&needCreate=false&type=normal&number=' + num);
- $('#openedBuildBox' + num).load(link, function(){$(this).find('select').val(oldOpenedBuild).picker({optionRender: markReleaseBuilds, dropWidth: 'auto'})});
+ $('#openedBuildBox' + num).load(link, function(){$(this).find('select').val(oldOpenedBuild).picker({optionRender: markReleaseBuilds})});
oldResolvedBuild = $('#resolvedBuild').val() ? $('#resolvedBuild').val() : 0;
link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=resolvedBuild&build=' + oldResolvedBuild + '&branch=' + branch);
@@ -973,3 +985,20 @@ function setBranchRelated(branchID, productID, num)
$.ajaxSettings.async = true;
}
}
+
+/**
+ * Set title field width.
+ *
+ * @access public
+ * @return void
+ */
+function setTitleWidth()
+{
+ if(window.config.currentMethod != 'browse') return false;
+
+ if($('#bugList thead th.c-title').width() < 150) $('#bugList thead th.c-title').width(150);
+ setTimeout(function()
+ {
+ if($('#bugList thead th.c-title').width() < 150) $('#bugList thead th.c-title').width(150);
+ }, 400)
+}
diff --git a/module/bug/js/edit.js b/module/bug/js/edit.js
index c077912cab..c827f33982 100644
--- a/module/bug/js/edit.js
+++ b/module/bug/js/edit.js
@@ -12,9 +12,8 @@ $(function()
confirmResult = confirm(confirmUnlinkBuild);
if(!confirmResult)
{
- $('#resolvedBuild').val(oldResolvedBuild);
- $('#resolvedBuild').trigger("chosen:updated");
- $('#resolvedBuild').chosen();
+ var resolvedBuildPicker = $('#resolvedBuild').data('zui.picker');
+ resolvedBuildPicker.setValue(oldResolvedBuild);
}
}
});
@@ -40,6 +39,9 @@ $(function()
var modalTrigger = new $.zui.ModalTrigger({type: 'iframe', width: '95%', url: link});
modalTrigger.show();
});
+
+ var $pkResolvedBuild = $('#pk_resolvedBuild-search');
+ $pkResolvedBuild.closest('.picker').css('width', $pkResolvedBuild.closest('td').width() - $pkResolvedBuild.closest('td').find('.input-group-btn').width());
});
/**
diff --git a/module/bug/lang/zh-cn.php b/module/bug/lang/zh-cn.php
index abb3839866..4dfb3b483b 100644
--- a/module/bug/lang/zh-cn.php
+++ b/module/bug/lang/zh-cn.php
@@ -13,7 +13,7 @@
$lang->bug->common = 'Bug';
$lang->bug->id = 'Bug编号';
$lang->bug->product = '所属' . $lang->productCommon;
-$lang->bug->branch = '分支/平台';
+$lang->bug->branch = '平台/分支';
$lang->bug->productplan = '所属' . '计划';
$lang->bug->module = '所属模块';
$lang->bug->moduleAB = '模块';
diff --git a/module/bug/model.php b/module/bug/model.php
index 0bcbe446fd..dad2de58be 100644
--- a/module/bug/model.php
+++ b/module/bug/model.php
@@ -1855,7 +1855,7 @@ class bugModel extends model
->andWhere('deleted')->eq(0)
->beginIF($excludeBugs)->andWhere('id')->notIN($excludeBugs)->fi()
->beginIF(!empty($productID) and strpos($bugQuery, 'product') === false and strpos($bugQuery, '`product` IN') === false)->andWhere('product')->eq($productID)->fi()
- ->beginIF(!empty($productID) and strpos($bugQuery, 'product') === false and strpos($bugQuery, '`product` IN') === false)->andWhere('branch')->eq($branchID)->fi()
+ ->beginIF(!empty($productID) and strpos($bugQuery, 'product') === false and strpos($bugQuery, '`product` IN') === false and $branchID != 'all')->andWhere('branch')->eq($branchID)->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll('id');
@@ -2319,7 +2319,7 @@ class bugModel extends model
$products = $this->session->product;
preg_match('/`product` IN \((?P.+)\)/', $this->reportCondition(), $matchs);
if(!empty($matchs) and isset($matchs['productIdList'])) $products = str_replace('\'', '', $matchs['productIdList']);
- $builds = $this->loadModel('build')->getBuildPairs($products, $branch = 0, $params = 'hasDeleted');
+ $builds = $this->loadModel('build')->getBuildPairs($products, $branch = 0, $params = 'hasdeleted');
/* Deal with the situation that a bug maybe associate more than one openedBuild. */
foreach($datas as $buildIDList => $data)
@@ -3023,6 +3023,8 @@ class bugModel extends model
}
$allBranch = "`branch` = 'all'";
+ $branch = trim($branch, ',');
+ if(strpos($branch, ',') !== false) $branch = str_replace(',', "','", $branch);
if($branch !== 'all' and strpos($bugQuery, '`branch` =') === false) $bugQuery .= " AND `branch` in('0','$branch')";
if(strpos($bugQuery, $allBranch) !== false) $bugQuery = str_replace($allBranch, '1', $bugQuery);
diff --git a/module/bug/view/activate.html.php b/module/bug/view/activate.html.php
index 37cc6027b6..4ea8e2bab9 100644
--- a/module/bug/view/activate.html.php
+++ b/module/bug/view/activate.html.php
@@ -58,6 +58,6 @@
product);
-js::set('releasedBuild', $lang->build->releasedBuild);
+js::set('released', $lang->build->released);
?>
diff --git a/module/bug/view/batchcreate.html.php b/module/bug/view/batchcreate.html.php
index eb34d2370a..6e62fc7d47 100755
--- a/module/bug/view/batchcreate.html.php
+++ b/module/bug/view/batchcreate.html.php
@@ -14,7 +14,7 @@
include '../../common/view/header.html.php';
js::set('requiredFields', $config->bug->create->requiredFields);
js::set('productID', $productID);
-js::set('releasedBuild', $lang->build->releasedBuild);
+js::set('released', $lang->build->released);
?>
bug->mySelects[$browseType]) && in_array($brow
{
$actionLink = $this->createLink('bug', 'batchResolve', "resolution=fixed&resolvedBuild=$key");
echo "";
- echo html::a('javascript:;', (in_array($key, $releasedBuilds) ? "{$lang->build->releasedBuild} " : '') . $build, '', "onclick=\"setFormAction('$actionLink', 'hiddenwin', '#bugList')\"");
+ echo html::a('javascript:;', $build . (in_array($key, $releasedBuilds) ? " {$lang->build->released} " : ''), '', "onclick=\"setFormAction('$actionLink', 'hiddenwin', '#bugList')\"");
echo " ";
}
echo "";
diff --git a/module/bug/view/create.html.php b/module/bug/view/create.html.php
index 3cb9f567a2..27819e3c62 100644
--- a/module/bug/view/create.html.php
+++ b/module/bug/view/create.html.php
@@ -31,7 +31,7 @@ js::set('requiredFields', $config->bug->create->requiredFields);
js::set('showFields', $showFields);
js::set('projectExecutionPairs', $projectExecutionPairs);
js::set('productID', $productID);
-js::set('releasedBuild', $lang->build->releasedBuild);
+js::set('released', $lang->build->released);
if($this->app->tab == 'execution') js::set('objectID', zget($execution, 'id', ''));
if($this->app->tab == 'project') js::set('objectID', $projectID);
?>
diff --git a/module/bug/view/edit.html.php b/module/bug/view/edit.html.php
index 5abdec20a3..10ad3fdc16 100644
--- a/module/bug/view/edit.html.php
+++ b/module/bug/view/edit.html.php
@@ -31,7 +31,7 @@ js::set('bugBranch' , $bug->branch);
js::set('isClosedBug' , $bug->status == 'closed');
js::set('projectExecutionPairs' , $projectExecutionPairs);
js::set('productID' , $product->id);
-js::set('releasedBuild' , $lang->build->releasedBuild);
+js::set('released' , $lang->build->released);
if($this->app->tab == 'execution') js::set('objectID', $bug->execution);
if($this->app->tab == 'project') js::set('objectID', $bug->project);
?>
diff --git a/module/bug/view/resolve.html.php b/module/bug/view/resolve.html.php
index b284f83a5c..78ed516c22 100644
--- a/module/bug/view/resolve.html.php
+++ b/module/bug/view/resolve.html.php
@@ -16,7 +16,7 @@
product);
-js::set('releasedBuild', $lang->build->releasedBuild);
+js::set('released', $lang->build->released);
?>
diff --git a/module/build/control.php b/module/build/control.php
index f2772a442f..8dc4494c0e 100644
--- a/module/build/control.php
+++ b/module/build/control.php
@@ -144,6 +144,8 @@ class build extends control
$changes = $this->build->update($buildID);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$files = $this->loadModel('file')->saveUpload('build', $buildID);
+ $change[$buildID] = $changes;
+ $this->unlinkOldBranch($change);
if($changes or $files)
{
@@ -162,6 +164,7 @@ class build extends control
$this->loadModel('execution');
$this->loadModel('product');
$build = $this->build->getById((int)$buildID);
+ $oldBranch = array($buildID => $build->branch);
/* Set menu. */
if($this->app->tab == 'project') $this->loadModel('project')->setMenu($build->project);
@@ -177,10 +180,11 @@ class build extends control
{
$productGroups = $this->product->getProducts($build->project);
$branches = $this->loadModel('branch')->getList($build->product, $build->project, 'all');
- $builds = $this->build->getBuildPairs($build->product, $build->branch, 'noempty,notrunk,singled,separate', $build->project, 'project', $build->builds, false);
+ $builds = $this->build->getBuildPairs($build->product, 'all', 'noempty,notrunk,singled,separate', $build->project, 'project', $build->builds, false);
}
$executions = $this->product->getExecutionPairsByProduct($build->product, $build->branch, 'id_desc', $this->session->project, 'stagefilter');
+ if($build->execution and !isset($executions[$build->execution])) $executions[$build->execution] = $this->loadModel('execution')->getById($build->execution)->name;
/* Get stories and bugs. */
$orderBy = 'status_asc, stage_asc, id_desc';
@@ -198,7 +202,10 @@ class build extends control
{
$branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
}
- if(!isset($branchTagOption[$build->branch])) $branchTagOption[$build->branch] = $this->branch->getById($build->branch, 0, 'name');
+ foreach(explode(',', $build->branch) as $buildBranch)
+ {
+ if(!isset($branchTagOption[$buildBranch])) $branchTagOption[$buildBranch] = $this->branch->getById($buildBranch, 0, 'name');
+ }
foreach($productGroups as $product) $products[$product->id] = $product->name;
@@ -209,6 +216,7 @@ class build extends control
$this->view->product = isset($productGroups[$build->product]) ? $productGroups[$build->product] : '';
$this->view->branchTagOption = $branchTagOption;
$this->view->orderBy = $orderBy;
+ $this->view->oldBranch = $oldBranch;
$this->view->executions = $executions;
$this->view->productGroups = $productGroups;
$this->view->products = $products;
@@ -279,6 +287,8 @@ class build extends control
foreach($stages as $storyID => $stage) $stories[$storyID]->stage = $stage;
/* Set menu. */
+ $objectType = 'execution';
+ $objectID = $build->execution;
if($this->app->tab == 'project')
{
$this->loadModel('project')->setMenu($build->project);
@@ -305,6 +315,16 @@ class build extends control
$this->view->generatedBugPager = $generatedBugPager;
$this->executeHooks($buildID);
+ $branchName = '';
+ if($build->productType != 'normal')
+ {
+ foreach(explode(',', $build->branch) as $buildBranch)
+ {
+ $branchName .= $this->loadModel('branch')->getById($buildBranch);
+ $branchName .= ',';
+ }
+ $branchName = trim($branchName, ',');
+ }
/* Assign. */
$this->view->canBeChanged = common::canBeChanged('build', $build); // Determines whether an object is editable.
@@ -320,7 +340,8 @@ class build extends control
$this->view->bugs = $bugs;
$this->view->type = $type;
$this->view->bugPager = $bugPager;
- $this->view->branchName = $build->productType == 'normal' ? '' : $this->loadModel('branch')->getById($build->branch);
+ $this->view->branchName = empty($branchName) ? $this->lang->branch->main : $branchName;
+ $this->view->childBuilds = empty($build->builds) ? array() : $this->dao->select('id,name,bugs,stories')->from(TABLE_BUILD)->where('id')->in($build->builds)->fetchAll();
if($this->app->getViewType() == 'json')
{
@@ -413,7 +434,7 @@ class build extends control
return print(html::select($varName, $builds, $build, "class='form-control'"));
}
- $builds = $this->build->getBuildPairs($productID, $branch, $type, 0, 'project', $build);
+ $builds = $this->build->getBuildPairs($productID, $branch, $type, 0, 'project', $build, false);
if(strpos($extra, 'multiple') !== false) $varName .= '[]';
if($isJsonView) return print(json_encode($builds));
return print(html::select($varName, $builds, $build, "class='form-control chosen' $extra"));
@@ -624,8 +645,16 @@ class build extends control
else
{
$branchPairs = $this->loadModel('branch')->getPairs($build->product, 'noempty');
- $branches = array('' => '') + array(BRANCH_MAIN => $this->lang->branch->main);
- if($build->branch) $branches += array($build->branch => $branchPairs[$build->branch]);
+ $branchAll = sprintf($this->lang->build->branchAll, $this->lang->product->branchName[$product->type]);
+ $branches = array('' => $branchAll) + array(BRANCH_MAIN => $this->lang->branch->main);
+ if($build->branch)
+ {
+ foreach(explode(',', $build->branch) as $branchID)
+ {
+ if($branchID == '0') continue;
+ $branches += array($branchID => $branchPairs[$branchID]);
+ }
+ }
$this->config->product->search['fields']['branch'] = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
$this->config->product->search['params']['branch']['values'] = $branches;
@@ -655,22 +684,82 @@ class build extends control
/**
* Unlink story
*
+ * @param int $buildID
* @param int $storyID
* @param string $confirm yes|no
* @access public
* @return void
*/
- public function unlinkStory($buildID, $storyID)
+ public function unlinkStory($buildID, $storyID, $confirm = 'no')
{
- $this->build->unlinkStory($buildID, $storyID);
+ if($confirm == 'no')
+ {
+ return print(js::confirm($this->lang->build->confirmUnlinkStory, inlink('unlinkstory', "buildID=$buildID&storyID=$storyID&confirm=yes")));
+ }
+ else
+ {
+ $this->build->unlinkStory($buildID, $storyID);
+ $this->loadModel('action')->create('build', $buildID, 'unlinkstory', '', $storyID);
+ return print(js::reload('parent'));
+ }
+ }
- return print(js::reload('parent'));
+ /**
+ * AJAX: Get unlinkBranch story and bug.
+ *
+ * @param int $buildID
+ * @param int $newBranch
+ * @access public
+ * @return void
+ */
+ public function ajaxGetBranch($buildID, $newBranch)
+ {
+ $build = $this->build->getByID($buildID);
+ $oldBranch = $build->branch;
+ $buildStories = $build->allStories ? $this->loadModel('story')->getByList($build->allStories) : array();
+ $buildBugs = $build->allBugs ? $this->loadModel('bug')->getByList($build->allBugs) : array();
+ $branchPairs = $this->loadModel('branch')->getPairs($build->product);
+ $typeName = $this->lang->product->branchName[$build->productType];
+
+ $removeBranches = '';
+ foreach(explode(',', $oldBranch) as $oldBranchID)
+ {
+ if($oldBranchID and strpos(",$newBranch,", ",$oldBranchID,") === false) $removeBranches .= "{$branchPairs[$oldBranchID]},";
+ }
+
+ $unlinkStoryCounts = 0;
+ $unlinkBugCounts = 0;
+ if($oldBranch)
+ {
+ foreach($buildStories as $storyID => $story)
+ {
+ if($story->branch and strpos(",$newBranch,", ",$story->branch,") === false) $unlinkStoryCounts ++;
+ }
+
+ foreach($buildBugs as $bugID => $bug)
+ {
+ if($bug->branch and strpos(",$newBranch,", ",$bug->branch,") === false) $unlinkBugCounts ++;
+ }
+ }
+
+ if($unlinkStoryCounts and $unlinkBugCounts)
+ {
+ printf($this->lang->build->confirmChangeBuild, $typeName, trim($removeBranches, ','), $typeName, $unlinkStoryCounts, $unlinkBugCounts);
+ }
+ elseif($unlinkStoryCounts)
+ {
+ printf($this->lang->build->confirmRemoveStory, $typeName, trim($removeBranches, ','), $typeName, $unlinkStoryCounts);
+ }
+ elseif($unlinkBugCounts)
+ {
+ printf($this->lang->build->confirmRemoveBug, $typeName, trim($removeBranches, ','), $typeName, $unlinkBugCounts);
+ }
}
/**
* Batch unlink story.
*
- * @param string $confirm
+ * @param int $buildID
* @access public
* @return void
*/
@@ -680,6 +769,45 @@ class build extends control
return print(js::locate($this->createLink('build', 'view', "buildID=$buildID&type=story"), 'parent'));
}
+ /**
+ * Unlink story and bug when edit branch of build.
+ * @param array $changes
+ * @access protected
+ * @return void
+ */
+ protected function unlinkOldBranch($changes)
+ {
+ foreach($changes as $buildID => $changes)
+ {
+ $oldBranch = '';
+ $newBranch = '';
+ foreach($changes as $changeId => $change)
+ {
+ if($change['field'] == 'branch')
+ {
+ $oldBranch = $change['old'];
+ $newBranch = $change['new'];
+ break;
+ }
+ }
+ $build = $this->build->getByID($buildID);
+ $planStories = $this->loadModel('story')->getByList($build->allStories);
+ $planBugs = $this->loadModel('bug')->getByList($build->allBugs);
+ if($oldBranch)
+ {
+ foreach($planStories as $storyID => $story)
+ {
+ if($story->branch and strpos(",$newBranch,", ",$story->branch,") === false) $this->build->unlinkStory($buildID, $storyID);
+ }
+
+ foreach($planBugs as $bugID => $bug)
+ {
+ if($bug->branch and strpos(",$newBranch,", ",$bug->branch,") === false) $this->build->unlinkBug($buildID, $bugID);
+ }
+ }
+ }
+ }
+
/**
* Link bugs.
*
@@ -748,8 +876,13 @@ class build extends control
}
else
{
- $branchName = $this->loadModel('branch')->getById($build->branch);
- $branches = array('' => '', BRANCH_MAIN => $this->lang->branch->main, $build->branch => $branchName);
+ $buildBranch = array();
+ $branchList = $this->loadModel('branch')->getPairs($build->product, '', $build->execution);
+ $branchAll = sprintf($this->lang->build->branchAll, $this->lang->product->branchName[$product->type]);
+ $branches = array('' => $branchAll, BRANCH_MAIN => $this->lang->branch->main);
+ if(strpos($build->branch, ',') !== false) $buildBranch = explode(',', $build->branch);
+ foreach($buildBranch as $buildKey) $branches += array($buildKey => zget($branchList, $buildKey));
+
$this->config->bug->search['fields']['branch'] = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
$this->config->bug->search['params']['branch']['values'] = $branches;
diff --git a/module/build/css/view.css b/module/build/css/view.css
index 7c6550c08f..500f9fcc2b 100644
--- a/module/build/css/view.css
+++ b/module/build/css/view.css
@@ -1,8 +1,27 @@
.linkBox #queryBox .search-form .form-actions {padding-bottom: 5px;}
.linkBox .table-header {padding: 8px 15px; border-bottom: 1px solid #cbd0db;}
#unlinkStoryList, #unlinkBugList {border-top: 1px solid #cbd0db;}
-.page-title .dropdown-menu {top: 20px; left: 170px;}
+.page-title .dropdown-menu {top: 20px; left: 170px; max-height:300px; overflow:auto;}
.page-title .text>a {font-weight: 700;}
.body-modal #mainContent {min-height: 200px;}
td.article-content{overflow:auto !important;}
+td.c-build{white-space: nowrap; overflow:hidden;}
+th.c-user,td.c-user{text-align:left;}
+
+#stories th, #stories td{padding-left:16px;padding-right:16px;}
+#stories .c-build{width:160px; text-align:left}
+#stories .c-estimate{width:90px; text-align:right}
+#stories .c-status{width:120px}
+#stories .c-stage{width:130px; text-align:left;}
+#bugs .c-status{width:100px;}
+#bugs .c-build{width:160px; text-align:left}
+#bugs .c-user{width:120px;}
+#bugs .c-date{width:130px;}
+#bugs .c-actions-1{width:60px;}
+#generatedBugs .c-id {width:60px;}
+#generatedBugs .c-severity {width:70px;}
+#generatedBugs .c-status {width:100px;}
+#generatedBugs .c-build {width:160px; text-align:left}
+#generatedBugs .c-user {width:100px;}
+#generatedBugs .c-date {width:120px;}
diff --git a/module/build/js/common.js b/module/build/js/common.js
index 4dbf5d9cec..8f0538b32f 100644
--- a/module/build/js/common.js
+++ b/module/build/js/common.js
@@ -7,6 +7,11 @@
*/
function loadBranches(productID)
{
+ if($('input[name=isIntegrated]:checked').val() == 'yes')
+ {
+ $('#branchBox').closest('tr').addClass('hidden');
+ return false;
+ }
$('#branch').remove();
$('#branch_chosen').remove();
var oldBranch = 0;
@@ -16,12 +21,17 @@ function loadBranches(productID)
}
projectID = currentTab == 'execution' ? executionID : projectID;
- $.get(createLink('branch', 'ajaxGetBranches', 'productID=' + productID + '&oldBranch=0¶m=active&projectID=' + projectID), function(data)
+ $.get(createLink('branch', 'ajaxGetBranches', 'productID=' + productID + '&oldBranch=0¶m=active&projectID=' + projectID + '&withMainBranch=true&isSiblings=no&fieldID=0&multiple=multiple'), function(data)
{
if(data)
{
- $('#product').closest('.input-group').append(data);
+ $('#branchBox').append(data);
$('#branch').chosen();
+ $('#branchBox').closest('tr').removeClass('hidden');
+ }
+ else
+ {
+ $('#branchBox').closest('tr').addClass('hidden');
}
});
}
diff --git a/module/build/js/create.js b/module/build/js/create.js
index b84d711c00..0626390b86 100644
--- a/module/build/js/create.js
+++ b/module/build/js/create.js
@@ -9,18 +9,23 @@ $().ready(function()
{
var projectID = $('#project').val();
var productID = $('#product').val();
- var branch = $('#branch').length > 0 ? $('#branch').val() : '';
- $.get(createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=builds&build=&branch=' + branch + '&index=&needCreate=&type=noempty,notrunk,separate,singled&extra=multiple'), function(data)
+ $.get(createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=builds&build=&branch=all&index=&needCreate=&type=noempty,notrunk,separate,singled&extra=multiple'), function(data)
{
if(data) $('#buildBox').html(data);
$('#builds').attr('data-placeholder', multipleSelect).chosen();
});
+
+ $.get(createLink('product', 'ajaxGetProductById', 'produtID=' + productID), function(data)
+ {
+ $('#branchBox').closest('tr').find('th').text(data.branchName);
+ }, 'json');
});
$('input[name=isIntegrated]').change(function()
{
var projectID = $('#project').val();
var executionID = $('#execution').val();
+
if($(this).val() == 'no')
{
$('#execution').closest('tr').show();
@@ -31,7 +36,16 @@ $().ready(function()
{
$('#execution').closest('tr').hide();
$('#buildBox').closest('tr').show();
+
+ $.ajaxSettings.async = false;
loadProducts(projectID);
+ var productID = $('#product').val();
+ $.get(createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=builds&build=&branch=all&index=&needCreate=&type=noempty,notrunk,separate,singled&extra=multiple'), function(data)
+ {
+ if(data) $('#buildBox').html(data);
+ $('#builds').attr('data-placeholder', multipleSelect).chosen();
+ });
+ $.ajaxSettings.async = true;
}
});
$('#product').change();
@@ -67,9 +81,16 @@ function loadProducts(executionID)
$('#productBox').append(data);
$('#product').chosen();
+
+ $.get(createLink('product', 'ajaxGetProductById', 'produtID=' + $("#product").val()), function(data)
+ {
+ $('#branchBox').closest('tr').find('th').text(data.branchName);
+ }, 'json');
+
loadBranches($("#product").val());
}
});
+
loadLastBuild();
}
@@ -84,7 +105,7 @@ function loadLastBuild()
var isIntegrated = $('input[name=isIntegrated]:checked').val();
var projectID = $('#project').val();
var executionID = $('#execution').val();
- if(isIntegrated == 'yes') executionID = 0;
+ if(isIntegrated == 'yes') executionID = 0;
$.get(createLink('build', 'ajaxGetLastBuild', 'projectID=' + projectID + '&executionID=' + executionID), function(data)
{
$('#lastBuildBox').html(data);
diff --git a/module/build/js/edit.js b/module/build/js/edit.js
index 7088791ec2..885275d499 100644
--- a/module/build/js/edit.js
+++ b/module/build/js/edit.js
@@ -3,20 +3,39 @@ $().ready(function()
var oldExecutionID = $('#execution').val();
$(document).on('change', '#product, #branch', function()
{
+ var productID = $('#product').val();
+
if(executionID)
{
loadExecutions(oldExecutionID);
}
else
{
- var productID = $('#product').val();
- var branch = $('#branch').val();
- $.get(createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=builds&build=&branch=' + branch + '&index=&needCreate=&type=noempty,notrunk,separate,singled&extra=multiple'), function(data)
+ $.get(createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=builds&build=&branch=all&index=&needCreate=&type=noempty,notrunk,separate,singled&extra=multiple'), function(data)
{
if(data) $('#buildBox').html(data);
$('#builds').attr('data-placeholder', multipleSelect).chosen();
});
}
+
+ var newBranch = $('#branch').val() ? $('#branch').val().toString() : '';
+ $.get(createLink('build', 'ajaxGetBranch', 'buildID=' + buildID + '&newBranch=' + newBranch), function(unlinkBranch)
+ {
+ if(unlinkBranch != '')
+ {
+ var result = confirm(unlinkBranch) ? true : false;
+ if(!result)
+ {
+ $('#branch').val(oldBranch[buildID].split(','));
+ $('#branch').trigger("chosen:updated");
+ }
+ }
+ });
+
+ $.get(createLink('product', 'ajaxGetProductById', 'produtID=' + productID), function(data)
+ {
+ $('#branchBox').closest('tr').find('th').text(data.branchName);
+ }, 'json');
});
});
diff --git a/module/build/lang/de.php b/module/build/lang/de.php
index 0164196034..658b48f285 100644
--- a/module/build/lang/de.php
+++ b/module/build/lang/de.php
@@ -31,15 +31,17 @@ $lang->build->id = 'ID';
$lang->build->product = $lang->productCommon;
$lang->build->project = 'Project';
$lang->build->branch = 'Platform/Branch';
+$lang->build->branchAll = 'All associated %s';
$lang->build->branchName = '%s';
$lang->build->execution = $lang->executionCommon;
$lang->build->integrated = 'Integrated';
$lang->build->singled = 'Singled';
$lang->build->builds = 'Included Builds';
-$lang->build->releasedBuild = 'Released Build';
+$lang->build->released = 'Released';
$lang->build->name = 'Name';
$lang->build->date = 'Datum';
$lang->build->builder = 'Builder';
+$lang->build->url = 'URL';
$lang->build->scmPath = 'SCM Pfad';
$lang->build->filePath = 'Dateipfad';
$lang->build->desc = 'Beschreibung';
@@ -54,7 +56,8 @@ $lang->build->bugs = 'Gelöster Bug';
$lang->build->generatedBugs = 'Left Bug';
$lang->build->noProduct = "
Dieses {$lang->executionCommon} ist nicht mit einem {$lang->productCommon} verknüpft, daher kann das Build nicht erstellt werden. Bitte erst {$lang->productCommon} verknüpfen. ";
$lang->build->noBuild = 'Keine Builds. ';
-$lang->build->emptyExecution = $lang->executionCommon . 'should be not empty.';
+$lang->build->emptyExecution = $lang->executionCommon . 'should be not empty.';
+$lang->build->linkedBuild = 'Linked Build';
$lang->build->notice = new stdclass();
$lang->build->notice->changeProduct = "The {$lang->SRCommon}, bug, or the version of the submitted test order has been linked, and its {$lang->productCommon} cannot be modified";
@@ -63,6 +66,11 @@ $lang->build->notice->changeBuilds = "The version of the submitted test order
$lang->build->notice->autoRelation = "The completed requirements, resolved bugs, and generated bugs under the relevant version will be automatically associated with the project version";
$lang->build->notice->createTest = "The execution of this version has been deleted, and the test cannot be submitted";
+$lang->build->confirmChangeBuild = "After branch『%s』disassociation,under the %s have %s {$lang->SRCommon} and %s Bug will remove synchronization from version, whether to cancel?";
+$lang->build->confirmRemoveStory = "After branch『%s』disassociation,under the %s have %s {$lang->SRCommon} will remove synchronization from version, whether to cancel?";
+$lang->build->confirmRemoveBug = "After branch『%s』disassociation,under the %s have %s Bug will remove synchronization from version, whether to cancel?";
+$lang->build->confirmRemoveTips = "Are you sure to delete %s『%s』?";
+
$lang->build->finishStories = " %s {$lang->SRCommon} sind abgeschlossen.";
$lang->build->resolvedBugs = ' %s Bugs sind gelöst.';
$lang->build->createdBugs = ' %s Bugs wurden erstellt.';
diff --git a/module/build/lang/en.php b/module/build/lang/en.php
index 75343df3d0..c877b2bf25 100644
--- a/module/build/lang/en.php
+++ b/module/build/lang/en.php
@@ -31,15 +31,17 @@ $lang->build->id = 'ID';
$lang->build->product = $lang->productCommon;
$lang->build->project = 'Project';
$lang->build->branch = 'Platform/Branch';
+$lang->build->branchAll = 'All associated %s';
$lang->build->branchName = '%s';
$lang->build->execution = $lang->executionCommon;
$lang->build->integrated = 'Integrated';
$lang->build->singled = 'Singled';
$lang->build->builds = 'Included Builds';
-$lang->build->releasedBuild = 'Released Build';
+$lang->build->released = 'Released';
$lang->build->name = 'Name';
$lang->build->date = 'Date';
$lang->build->builder = 'Builder';
+$lang->build->url = 'URL';
$lang->build->scmPath = 'SCM Path';
$lang->build->filePath = 'File Path';
$lang->build->desc = 'Description';
@@ -54,7 +56,8 @@ $lang->build->bugs = 'Resolved Bugs';
$lang->build->generatedBugs = 'Reported Bugs';
$lang->build->noProduct = "
This {$lang->executionCommon} is not linked to {$lang->productCommon}, so the Build cannot be created. Please first link {$lang->productCommon} ";
$lang->build->noBuild = 'No builds yet.';
-$lang->build->emptyExecution = $lang->executionCommon . 'should be not empty.';
+$lang->build->emptyExecution = $lang->executionCommon . 'should be not empty.';
+$lang->build->linkedBuild = 'Linked Build';
$lang->build->notice = new stdclass();
$lang->build->notice->changeProduct = "The {$lang->SRCommon}, bug, or the version of the submitted test order has been linked, and its {$lang->productCommon} cannot be modified";
@@ -63,6 +66,11 @@ $lang->build->notice->changeBuilds = "The version of the submitted test order
$lang->build->notice->autoRelation = "The completed requirements, resolved bugs, and generated bugs under the relevant version will be automatically associated with the project version";
$lang->build->notice->createTest = "The execution of this version has been deleted, and the test cannot be submitted";
+$lang->build->confirmChangeBuild = "After branch『%s』disassociation,under the %s have %s {$lang->SRCommon} and %s Bug will remove synchronization from version, whether to cancel?";
+$lang->build->confirmRemoveStory = "After branch『%s』disassociation,under the %s have %s {$lang->SRCommon} will remove synchronization from version, whether to cancel?";
+$lang->build->confirmRemoveBug = "After branch『%s』disassociation,under the %s have %s Bug will remove synchronization from version, whether to cancel?";
+$lang->build->confirmRemoveTips = "Are you sure to delete %s『%s』?";
+
$lang->build->finishStories = " Finished {$lang->SRCommon} %s";
$lang->build->resolvedBugs = ' Resolved Bug %s';
$lang->build->createdBugs = ' Reported Bug %s';
diff --git a/module/build/lang/fr.php b/module/build/lang/fr.php
index e6500fd173..33f4168700 100644
--- a/module/build/lang/fr.php
+++ b/module/build/lang/fr.php
@@ -31,15 +31,17 @@ $lang->build->id = 'ID';
$lang->build->product = $lang->productCommon;
$lang->build->project = 'Project';
$lang->build->branch = 'Plateforme/Branche';
+$lang->build->branchAll = 'All associated %s';
$lang->build->branchName = '%s';
$lang->build->execution = $lang->executionCommon;
$lang->build->integrated = 'Integrated';
$lang->build->singled = 'Singled';
$lang->build->builds = 'Included Builds';
-$lang->build->releasedBuild = 'Released Build';
+$lang->build->released = 'Released';
$lang->build->name = 'Nom';
$lang->build->date = 'Date';
$lang->build->builder = 'Builder';
+$lang->build->url = 'URL';
$lang->build->scmPath = 'Chemin SCM';
$lang->build->filePath = 'Chemin Fichier';
$lang->build->desc = 'Description';
@@ -54,7 +56,8 @@ $lang->build->bugs = 'Bugs Résolus';
$lang->build->generatedBugs = 'Bugs signalés';
$lang->build->noProduct = "
Ce {$lang->executionCommon} n'est pas associé à un {$lang->productCommon}, le Build ne peut pas être créé. Commencez par rattacher le projet à un {$lang->productCommon} ";
$lang->build->noBuild = 'Aucun builds.';
-$lang->build->emptyExecution = $lang->executionCommon . 'should be not empty.';
+$lang->build->emptyExecution = $lang->executionCommon . 'should be not empty.';
+$lang->build->linkedBuild = 'Linked Build';
$lang->build->notice = new stdclass();
$lang->build->notice->changeProduct = "The {$lang->SRCommon}, bug, or the version of the submitted test order has been linked, and its {$lang->productCommon} cannot be modified";
@@ -63,6 +66,11 @@ $lang->build->notice->changeBuilds = "The version of the submitted test order
$lang->build->notice->autoRelation = "The completed requirements, resolved bugs, and generated bugs under the relevant version will be automatically associated with the project version";
$lang->build->notice->createTest = "The execution of this version has been deleted, and the test cannot be submitted";
+$lang->build->confirmChangeBuild = "After branch『%s』disassociation,under the %s have %s {$lang->SRCommon} and %s Bug will remove synchronization from version, whether to cancel?";
+$lang->build->confirmRemoveStory = "After branch『%s』disassociation,under the %s have %s {$lang->SRCommon} will remove synchronization from version, whether to cancel?";
+$lang->build->confirmRemoveBug = "After branch『%s』disassociation,under the %s have %s Bug will remove synchronization from version, whether to cancel?";
+$lang->build->confirmRemoveTips = "Are you sure to delete %s『%s』?";
+
$lang->build->finishStories = " {$lang->SRCommon} Terminées %s";
$lang->build->resolvedBugs = ' Bugs Résolus %s';
$lang->build->createdBugs = ' Bugs Signalés %s';
diff --git a/module/build/lang/zh-cn.php b/module/build/lang/zh-cn.php
index fe804daf9c..a8c80bbd5b 100644
--- a/module/build/lang/zh-cn.php
+++ b/module/build/lang/zh-cn.php
@@ -31,15 +31,17 @@ $lang->build->id = 'ID';
$lang->build->product = '所属' . $lang->productCommon;
$lang->build->project = '所属项目';
$lang->build->branch = '平台/分支';
+$lang->build->branchAll = '所有关联%s';
$lang->build->branchName = '所属%s';
$lang->build->execution = '所属' . $lang->executionCommon;
$lang->build->integrated = '集成版本';
$lang->build->singled = '单一版本';
$lang->build->builds = '包含版本';
-$lang->build->releasedBuild = '发布版本';
+$lang->build->released = '发布';
$lang->build->name = '名称编号';
$lang->build->date = '打包日期';
$lang->build->builder = '构建者';
+$lang->build->url = '地址';
$lang->build->scmPath = '源代码地址';
$lang->build->filePath = '下载地址';
$lang->build->desc = '描述';
@@ -54,7 +56,8 @@ $lang->build->bugs = '解决的Bug';
$lang->build->generatedBugs = '产生的Bug';
$lang->build->noProduct = "
该{$lang->executionCommon}没有关联{$lang->productCommon},无法创建版本,请先关联{$lang->productCommon} ";
$lang->build->noBuild = '暂时没有版本。';
-$lang->build->emptyExecution = $lang->executionCommon . '不能为空。';
+$lang->build->emptyExecution = $lang->executionCommon . '不能为空。';
+$lang->build->linkedBuild = '关联版本';
$lang->build->notice = new stdclass();
$lang->build->notice->changeProduct = "已经关联{$lang->SRCommon}、Bug或提交测试单的版本,不能修改其所属{$lang->productCommon}";
@@ -63,6 +66,11 @@ $lang->build->notice->changeBuilds = "提交测试单的版本,不能修改
$lang->build->notice->autoRelation = "相关版本下完成的需求、解决的Bug、产生的Bug将会自动关联到项目版本中";
$lang->build->notice->createTest = "该版本所属执行已删除,不能提交测试";
+$lang->build->confirmChangeBuild = "%s『%s』解除关联后,%s下 %s个{$lang->SRCommon}和%s个Bug将同步从版本移除,是否解除?";
+$lang->build->confirmRemoveStory = "%s『%s』解除关联后,%s下 %s个{$lang->SRCommon}将同步从计划中移除,是否解除?";
+$lang->build->confirmRemoveBug = "%s『%s』解除关联后,%s下 %s个Bug将同步从计划中移除,是否解除?";
+$lang->build->confirmRemoveTips = "确认删除%s『%s』吗?";
+
$lang->build->finishStories = " 本次共完成 %s 个{$lang->SRCommon}";
$lang->build->resolvedBugs = ' 本次共解决 %s 个Bug';
$lang->build->createdBugs = ' 本次共产生 %s 个Bug';
diff --git a/module/build/lang/zh-tw.php b/module/build/lang/zh-tw.php
index 55b8b0934e..89d85f8281 100644
--- a/module/build/lang/zh-tw.php
+++ b/module/build/lang/zh-tw.php
@@ -31,6 +31,7 @@ $lang->build->id = 'ID';
$lang->build->product = $lang->productCommon;
$lang->build->project = '所屬項目';
$lang->build->branch = '平台/分支';
+$lang->build->branchAll = '所有關聯%s';
$lang->build->branchName = '所屬%s';
$lang->build->execution = '所屬' . $lang->executionCommon;
$lang->build->name = '名稱編號';
diff --git a/module/build/model.php b/module/build/model.php
index fd739b41a0..9781b36b30 100644
--- a/module/build/model.php
+++ b/module/build/model.php
@@ -63,11 +63,10 @@ class buildModel extends model
*/
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')
+ return $this->dao->select('t1.*, t2.name as executionName, t2.id as executionID, t2.deleted as executionDeleted, t3.name as productName')
->from(TABLE_BUILD)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')
->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id')
- ->leftJoin(TABLE_BRANCH)->alias('t4')->on('t1.branch = t4.id')
->where('t1.deleted')->eq(0)
->andWhere('t1.project')->ne(0)
->beginIF($projectID)->andWhere('t1.project')->eq((int)$projectID)->fi()
@@ -129,11 +128,10 @@ class buildModel extends model
*/
public function getExecutionBuilds($executionID, $type = '', $param = '', $orderBy = 't1.date_desc,t1.id_desc', $pager = null)
{
- return $this->dao->select('t1.*, t2.name as executionName, t3.name as productName, t4.name as branchName')
+ return $this->dao->select('t1.*, t2.name as executionName, t3.name as productName')
->from(TABLE_BUILD)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')
->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id')
- ->leftJoin(TABLE_BRANCH)->alias('t4')->on('t1.branch = t4.id')
->where('t1.deleted')->eq(0)
->beginIF($executionID)->andWhere('t1.execution')->eq((int)$executionID)->fi()
->beginIF($type == 'product' and $param)->andWhere('t1.product')->eq($param)->fi()
@@ -255,9 +253,11 @@ class buildModel extends model
->beginIF($products)->andWhere('product')->in($products)->fi()
->beginIF($objectType === 'execution' and $objectID)->andWhere('execution')->eq($objectID)->fi()
->beginIF($objectType === 'project' and $objectID)->andWhere('project')->eq($objectID)->fi()
+ ->beginIF(strpos($params, 'hasdeleted') === false)->andWhere('deleted')->eq(0)->fi()
->fetchPairs();
}
+ $shadows = $this->dao->select('shadow')->from(TABLE_RELEASE)->where('product')->in($products)->fetchPairs('shadow', 'shadow');
$branchs = strpos($params, 'separate') === false ? "0,$branch" : $branch;
$allBuilds = $this->dao->select('t1.id, t1.name, t1.execution, t1.date, t1.deleted, t2.status as objectStatus, t3.id as releaseID, t3.status as releaseStatus, t4.name as branchName, t5.type as productType')->from(TABLE_BUILD)->alias('t1')
->beginIF($objectType === 'execution')->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')->fi()
@@ -266,7 +266,8 @@ class buildModel extends model
->leftJoin(TABLE_BRANCH)->alias('t4')->on('t1.branch = t4.id')
->leftJoin(TABLE_PRODUCT)->alias('t5')->on('t1.product = t5.id')
->where('1=1')
- ->beginIF(strpos($params, 'hasDeleted') === false)->andWhere('t1.deleted')->eq(0)->fi()
+ ->andWhere('t1.id')->notIN($shadows)
+ ->beginIF(strpos($params, 'hasdeleted') === false)->andWhere('t1.deleted')->eq(0)->fi()
->beginIF(strpos($params, 'hasproject') !== false)->andWhere('t1.project')->ne(0)->fi()
->beginIF(strpos($params, 'singled') !== false)->andWhere('t1.execution')->ne(0)->fi()
->beginIF($products)->andWhere('t1.product')->in($products)->fi()
@@ -327,7 +328,7 @@ class buildModel extends model
$releaseName = $release->name;
$branchName = $release->branchName ? $release->branchName : $this->lang->branch->main;
if($release->productType != 'normal') $releaseName = (strpos($params, 'withbranch') !== false ? $branchName . '/' : '') . $releaseName;
- if(strpos($params, 'releasetag') !== false) $releaseName = "[{$this->lang->build->releasedBuild}] " . $releaseName;
+ if(strpos($params, 'releasetag') !== false) $releaseName = $releaseName . " [{$this->lang->build->released}]";
$builds[$release->date][$release->shadow] = $releaseName;
foreach(explode(',', trim($release->build, ',')) as $buildID)
{
@@ -382,21 +383,44 @@ class buildModel extends model
->setDefault('product', 0)
->setDefault('branch', 0)
->setDefault('builds', '')
- ->cleanInt('product,branch')
+ ->cleanInt('product')
->add('createdBy', $this->app->user->account)
->add('createdDate', helper::now())
->stripTags($this->config->build->editor->create['id'], $this->config->allowedTags)
->join('builds', ',')
+ ->join('branch', ',')
->remove('resolvedBy,allchecker,files,labels,isIntegrated,uid')
->get();
- if($this->post->isIntegrated == 'yes') $build->execution = 0;
+ if($this->post->isIntegrated == 'yes')
+ {
+ $build->execution = 0;
+ $branchPairs = $this->dao->select('branch')->from(TABLE_BUILD)->where('id')->in($build->builds)->fetchPairs();
+ $relationBranch = array();
+ foreach($branchPairs as $branches)
+ {
+ foreach(explode(',', $branches) as $branch)
+ {
+ if(!isset($relationBranch[$branch])) $relationBranch[$branch] = $branch;
+ }
+ }
+ $build->branch = implode(',', $relationBranch);
+ }
+
+ $product = $this->loadModel('product')->getByID($build->product);
+ if(!empty($product) and $product->type != 'normal' and $this->post->isIntegrated == 'no' and !isset($_POST['branch']))
+ {
+ $this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
+ dao::$errors['branch'] = sprintf($this->lang->error->notempty, $this->lang->product->branch);
+ }
+
+ if(dao::isError()) return false;
$build = $this->loadModel('file')->processImgURL($build, $this->config->build->editor->create['id'], $this->post->uid);
$this->dao->insert(TABLE_BUILD)->data($build)
->autoCheck()
->batchCheck($this->config->build->create->requiredFields, 'notempty')
- ->check('name', 'unique', "product = {$build->product} AND branch = {$build->branch} AND deleted = '0'")
+ ->check('name', 'unique', "product = {$build->product} AND branch = '{$build->branch}' AND deleted = '0'")
->checkFlow()
->exec();
@@ -426,17 +450,67 @@ class buildModel extends model
->setIF(!isset($_POST['branch']), 'branch', $oldBuild->branch)
->setDefault('product', $oldBuild->product)
->setDefault('builds', '')
- ->cleanInt('product,branch,execution')
+ ->cleanInt('product,execution')
->join('builds', ',')
+ ->join('branch', ',')
->remove('allchecker,resolvedBy,files,labels,uid')
->get();
+ if(empty($oldBuild->execution))
+ {
+ $buildBranch = array();
+ foreach(explode(',', trim($build->branch, ',')) as $branchID) $buildBranch[$branchID] = $branchID;
+
+ /* Get delete builds. */
+ $deleteBuilds = array();
+ foreach(explode(',', $oldBuild->builds) as $oldBuildID)
+ {
+ if(empty($oldBuildID)) continue;
+ if(strpos(",$newBuilds,", ",$oldBuildID,") === false) $deleteBuilds[$oldBuildID] = $oldBuildID;
+ }
+
+ /* Delete the branch when the branch of the deleted build has no linked stories. */
+ $deleteBuildBranches = $this->dao->select('branch')->from(TABLE_BUILD)->where('id')->in($deleteBuilds)->fetchPairs();
+ $linkedStoryBranches = $this->dao->select('branch')->from(TABLE_STORY)->where('id')->in($oldBuild->stories)->fetchPairs('branch');
+ foreach($deleteBuildBranches as $deleteBuildBranch)
+ {
+ foreach(explode(',', $deleteBuildBranch) as $deleteBuildBranchID)
+ {
+ if(empty($deleteBuildBranchID) or isset($linkedStoryBranches[$deleteBuildBranchID])) continue;
+ unset($buildBranch[$deleteBuildBranchID]);
+ }
+ }
+
+ /* Add branch of new builds. */
+ $newBuilds = isset($build->builds) ? $build->builds : '';
+ $newBuildBranches = $this->dao->select('branch')->from(TABLE_BUILD)->where('id')->in($newBuilds)->fetchPairs();
+ foreach($newBuildBranches as $newBuildBranch)
+ {
+ foreach(explode(',', $newBuildBranch) as $newBuildBranchID)
+ {
+ if(empty($newBuildBranchID)) continue;
+ if(!isset($buildBranch[$newBuildBranchID])) $buildBranch[$newBuildBranchID] = $newBuildBranchID;
+ }
+ }
+
+ $build->branch = implode(',', $buildBranch);
+ }
+
+ $product = $this->loadModel('product')->getByID($build->product);
+ if(!empty($product) and $product->type != 'normal' and !isset($_POST['branch']) and isset($_POST['product']))
+ {
+ $this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
+ dao::$errors['branch'] = sprintf($this->lang->error->notempty, $this->lang->product->branch);
+ }
+
+ if(dao::isError()) return false;
+
$build = $this->loadModel('file')->processImgURL($build, $this->config->build->editor->edit['id'], $this->post->uid);
$this->dao->update(TABLE_BUILD)->data($build)
->autoCheck()
->batchCheck($this->config->build->edit->requiredFields, 'notempty')
->where('id')->eq($buildID)
- ->check('name', 'unique', "id != $buildID AND product = {$build->product} AND branch = {$build->branch} AND deleted = '0'")
+ ->check('name', 'unique', "id != $buildID AND product = {$build->product} AND branch = '{$build->branch}' AND deleted = '0'")
->checkFlow()
->exec();
if(isset($build->branch) and $oldBuild->branch != $build->branch) $this->dao->update(TABLE_RELEASE)->set('branch')->eq($build->branch)->where('build')->eq($buildID)->exec();
@@ -511,6 +585,7 @@ class buildModel extends model
$this->loadModel('action');
foreach($this->post->stories as $storyID) $this->action->create('story', $storyID, 'linked2build', '', $buildID);
+ $this->action->create('build', $buildID, 'linkstory', '', implode(',', $this->post->stories));
}
/**
@@ -628,8 +703,8 @@ class buildModel extends model
{
$build->allBugs = $build->bugs;
$build->allStories = $build->stories;
- $childBuilds = $this->dao->select('bugs, stories')->from(TABLE_BUILD)->where('id')->in($build->builds)->fetchAll();
+ $childBuilds = $this->dao->select('id,name,bugs,stories')->from(TABLE_BUILD)->where('id')->in($build->builds)->fetchAll();
foreach($childBuilds as $childBuild)
{
if($childBuild->bugs) $build->allBugs .= ",{$childBuild->bugs}";
@@ -687,7 +762,7 @@ class buildModel extends model
{
$executionID = $tab == 'execution' ? $extraParams['executionID'] : $build->execution;
$execution = $this->loadModel('execution')->getByID($executionID);
- $build->executionDeleted = $execution ? $execution->deleted : 0;
+ $build->executionDeleted = $execution ? $execution->deleted : 0;
$testtaskApp = (!empty($execution->type) and $execution->type == 'kanban') ? 'data-app="qa"' : "data-app='{$tab}'";
diff --git a/module/build/view/create.html.php b/module/build/view/create.html.php
index 543af32def..0309bcc865 100644
--- a/module/build/view/create.html.php
+++ b/module/build/view/create.html.php
@@ -33,12 +33,6 @@
id, "onchange='loadBranches(this.value);' class='form-control chosen' required");?>
- type != 'normal')
- {
- echo " " . html::select('branch', $branches, key($product->branches), "class='form-control chosen'");
- }
- ?>
@@ -50,6 +44,19 @@
+ '>
+
+ product->branchName[$productType]?>
+
+
+
+
+
+
build->builds;?>
build->placeholder->multipleSelect}'");?>
diff --git a/module/build/view/edit.html.php b/module/build/view/edit.html.php
index 21e386d8d3..fdaa8edaa1 100644
--- a/module/build/view/edit.html.php
+++ b/module/build/view/edit.html.php
@@ -32,16 +32,24 @@
?>
product, "onchange='loadBranches(this.value);' class='form-control chosen' $disabled required");?>
- productType != 'normal')
- {
- echo " " . html::select('branch', $branchTagOption, $build->branch, "class='form-control chosen' $disabled");
- }
- ?>
build->notice->changeProduct;?>
+
+ execution)) echo 'hidden'?>'>
+
+ type == 'normal' ? '' : $lang->product->branchName[$product->type]?>
+
+
+ branch, "class='form-control chosen' multiple required"); ?>
+
+
+
execution):?>
@@ -101,7 +109,9 @@
project)?>
+
builds)?>
+id)?>
execution)?>
app->tab);?>
build->placeholder->multipleSelect);?>
diff --git a/module/build/view/view.html.php b/module/build/view/view.html.php
index fe31522f6a..47cb7cf6ce 100644
--- a/module/build/view/view.html.php
+++ b/module/build/view/view.html.php
@@ -88,18 +88,35 @@ tbody tr td:first-child input {display: none;}
idAB);?>
- pri;?>>priAB);?>
story->title);?>
- openedByAB);?>
- story->estimateAB);?>
+ pri;?>>priAB);?>
statusAB);?>
- story->stageAB);?>
+
+ build->linkedBuild;?>
+
+ openedByAB);?>
+ story->estimateAB);?>
+ story->stageAB);?>
actions?>
$story):?>
- stories,", ",$story->id,") !== false ? '' : "disabled";?>
+ allStories,", ",$story->id,") !== false ? '' : "disabled";
+ $buildName = $build->name;
+ if($unlinkClass == 'disabled')
+ {
+ foreach($childBuilds as $childBuild)
+ {
+ if(strpos(",$childBuild->stories,", ",$story->id,") !== false)
+ {
+ $buildName = $childBuild->name;
+ break;
+ }
+ }
+ }
+ ?>
@@ -108,11 +125,6 @@ tbody tr td:first-child input {display: none;}
id);?>
-
- pri):?>
- story->priList, $story->pri, $story->pri);?>
-
-
parent > 0) echo "{$lang->story->childrenAB} ";
@@ -130,14 +142,20 @@ tbody tr td:first-child input {display: none;}
}
?>
- openedBy);?>
- estimate . $config->hourUnit;?>
-
- processStatus('story', $story);?>
-
+ pri):?>
+ story->priList, $story->pri, $story->pri);?>
+
- story->stageList[$story->stage];?>
+
+ processStatus('story', $story);?>
+
+
+
+
+ openedBy);?>
+ estimate . $config->hourUnit;?>
+ story->stageList[$story->stage];?>
idAB);?>
- bug->title);?>
- bug->status);?>
+ bug->title);?>
+ bug->status);?>
+
+ build->linkedBuild?>
+
openedByAB);?>
bug->openedDateAB);?>
- bug->resolvedByAB);?>
+ bug->resolvedBy);?>
bug->resolvedDateAB);?>
actions?>
- createLink('bug', 'view', "bugID=$bug->id", '', true);?>
- bugs,", ",$bug->id,") !== false ? '' : "disabled";?>
+ createLink('bug', 'view', "bugID=$bug->id", '', true);?>
+ allBugs,", ",$bug->id,") !== false ? '' : "disabled";
+ $buildName = $build->name;
+ if($unlinkClass == 'disabled')
+ {
+ foreach($childBuilds as $childBuild)
+ {
+ if(strpos(",$childBuild->bugs,", ",$bug->id,") !== false)
+ {
+ $buildName = $childBuild->name;
+ break;
+ }
+ }
+ }
+ ?>
@@ -216,10 +251,13 @@ tbody tr td:first-child input {display: none;}
processStatus('bug', $bug);?>
- openedBy);?>
- openedDate) ? '' : substr($bug->openedDate, 5, 11);?>
- resolvedBy);?>
- resolvedDate) ? '' : substr($bug->resolvedDate, 5, 11);?>
+
+
+
+ openedBy);?>
+ openedDate) ? '' : substr($bug->openedDate, 5, 11);?>
+ resolvedBy);?>
+ resolvedDate) ? '' : substr($bug->resolvedDate, 5, 11);?>
id}&type=generatedBug&link=$link¶m=$param&orderBy=%s";?>
- idAB);?>
- bug->severity;?>>bug->severityAB);?>
- bug->title);?>
- bug->status);?>
+ idAB);?>
+ bug->title);?>
+ bug->severity;?>>bug->severityAB);?>
+ bug->status);?>
+ bug->openedBuild;?>
openedByAB);?>
bug->openedDateAB);?>
- bug->resolvedByAB);?>
+ bug->resolvedBy);?>
bug->resolvedDateAB);?>
@@ -283,6 +322,9 @@ tbody tr td:first-child input {display: none;}
createLink('bug', 'view', "bugID=$bug->id", '', true);?>
id);?>
+
+ title, '', isonlybody() ? "data-width='1000'" : "class='iframe' data-width='1000'");?>
+
bug->severityList, $bug->severity, $bug->severity);?>
@@ -290,18 +332,20 @@ tbody tr td:first-child input {display: none;}
-
- title, '', isonlybody() ? "data-width='1000'" : "class='iframe' data-width='1000'");?>
-
processStatus('bug', $bug);?>
- openedBy);?>
- openedDate) ? '' : substr($bug->openedDate, 5, 11);?>
- resolvedBy);?>
- resolvedDate) ? '' : substr($bug->resolvedDate, 5, 11);?>
+ openedBuild) as $buildID) $openedBuilds .= ($buildID == 'trunk' ? 'Trunk' : zget($buildPairs, $buildID, '')) . ' ';
+ ?>
+
+ openedBy);?>
+ openedDate) ? '' : substr($bug->openedDate, 5, 11);?>
+ resolvedBy);?>
+ resolvedDate) ? '' : substr($bug->resolvedDate, 5, 11);?>
diff --git a/module/common/model.php b/module/common/model.php
index a1f7f2d40e..cd339bc545 100644
--- a/module/common/model.php
+++ b/module/common/model.php
@@ -395,9 +395,9 @@ class commonModel extends model
/**
* Deny access.
*
- * @param varchar $module
- * @param varchar $method
- * @param bool $reload
+ * @param string $module
+ * @param string $method
+ * @param bool $reload
* @access public
* @return mixed
*/
@@ -939,6 +939,38 @@ class commonModel extends model
echo "';
}
+ /**
+ * Format the date to YYYY-mm-dd, format the datetime to YYYY-mm-dd HH:ii:ss.
+ *
+ * @param int $date
+ * @param string $type date|datetime|''
+ * @access public
+ * @return string
+ */
+ public function formatDate($date, $type = '')
+ {
+ $datePattern = '\w{4}(\/|-)\w{1,2}(\/|-)\w{1,2}';
+ $datetimePattern = $datePattern . ' \w{1,2}\:\w{1,2}\:\w{1,2}';
+
+ if(empty($type))
+ {
+ if(!preg_match("/$datePattern/", $date) and !preg_match("/$datetimePattern/", $date)) return $date;
+ if(preg_match("/$datePattern/", $date) === 1) $type = 'date';
+ if(preg_match("/$datetimePattern/", $date) === 1) $type = 'datetime';
+ }
+
+ if(helper::isZeroDate($date))
+ {
+ if($type == 'date') return '0000-00-00';
+ if($type == 'datetime') return '0000-00-00 00:00:00';
+ }
+
+ if($type == 'date') $format = 'Y-m-d';
+ if($type == 'datetime') $format = 'Y-m-d H:i:s';
+
+ return date($format, strtotime($date));
+ }
+
/**
* Get main nav items list
*
@@ -2454,7 +2486,7 @@ EOD;
}
catch(EndResponseException $endResponseException)
{
- echo $endResponseException->getContent();
+ die($endResponseException->getContent());
}
}
diff --git a/module/convert/model.php b/module/convert/model.php
index dbb5055f98..41fd33e9e0 100644
--- a/module/convert/model.php
+++ b/module/convert/model.php
@@ -41,12 +41,13 @@ class convertModel extends model
* Check database exits or not.
*
* @access public
- * @return bool
+ * @return object|false
*/
public function dbExists($dbName = '')
{
- $sql = "SHOW DATABASES like '{$dbName}'";
- return $this->dbh->query($sql)->fetch();
+ $statement = $this->dbh->prepare('SHOW DATABASES like ?');
+ $statement->execute([$dbName]);
+ return $statement->fetch();
}
/**
diff --git a/module/dept/model.php b/module/dept/model.php
index 7fd5752c90..fa432bda96 100644
--- a/module/dept/model.php
+++ b/module/dept/model.php
@@ -371,10 +371,10 @@ class deptModel extends model
/**
* Get users of a deparment.
*
- * @param varchar $browseType inside|outside|all
+ * @param string $browseType inside|outside|all
* @param int $deptID
* @param object $pager
- * @param varchar $orderBy
+ * @param string $orderBy
* @access public
* @return array
*/
diff --git a/module/doc/view/browse.html.php b/module/doc/view/browse.html.php
index 0a22344249..16d5112f47 100644
--- a/module/doc/view/browse.html.php
+++ b/module/doc/view/browse.html.php
@@ -84,7 +84,7 @@
id&objectType=doc");?>" title="" class='btn btn-link ajaxCollect'>
- id&comment=false", " ", '', "title='{$lang->edit}' class='btn btn-link iframe'", true, true)?>
+ id&comment=false", " ", '', "title='{$lang->edit}' class='btn btn-link'", true, false)?>
id&confirm=no", " ", 'hiddenwin', "title='{$lang->delete}' class='btn btn-link'")?>
diff --git a/module/doc/view/content.html.php b/module/doc/view/content.html.php
index e0bf9ba33c..15dddb0a17 100644
--- a/module/doc/view/content.html.php
+++ b/module/doc/view/content.html.php
@@ -296,7 +296,7 @@
$(function()
{
var simplemde = new SimpleMDE({element: $("#markdownContent")[0],toolbar:false, status: false});
- simplemde.value(String(markdownText));
+ //simplemde.value(String(markdownText));
simplemde.togglePreview();
$('#content .CodeMirror .editor-preview a').attr('target', '_blank');
diff --git a/module/doc/view/view.html.php b/module/doc/view/view.html.php
index 5f3c5be6a7..9b7090080f 100644
--- a/module/doc/view/view.html.php
+++ b/module/doc/view/view.html.php
@@ -249,7 +249,7 @@ js::set('docID', $doc->id);
$(function()
{
var simplemde = new SimpleMDE({element: $("#markdownContent")[0],toolbar:false, status: false});
- simplemde.value(String(markdownText));
+ //simplemde.value(String(markdownText));
simplemde.togglePreview();
$('#content .CodeMirror .editor-preview a').attr('target', '_blank');
diff --git a/module/execution/control.php b/module/execution/control.php
index 6ea8d61fc6..30a6c981cd 100644
--- a/module/execution/control.php
+++ b/module/execution/control.php
@@ -898,7 +898,7 @@ class execution extends control
$storyBugs = $this->loadModel('bug')->getStoryBugCounts($storyIdList, $executionID);
$storyCases = $this->loadModel('testcase')->getStoryCaseCounts($storyIdList);
- $plans = $this->execution->getPlans($products, 'skipParent|withMainPlan');
+ $plans = $this->execution->getPlans($products, 'skipParent|withMainPlan|unexpired|noclosed', $executionID);
$allPlans = array('' => '');
if(!empty($plans))
{
@@ -934,6 +934,17 @@ class execution extends control
$moduleTree = $this->tree->getProjectStoryTreeMenu($executionID, 0, array('treeModel', $createModuleLink));
}
+ $executionProductList = $this->loadModel('product')->getProducts($executionID);
+ $multiBranch = false;
+ foreach($executionProductList as $executionProduct)
+ {
+ if($executionProduct->type != 'normal')
+ {
+ $multiBranch = true;
+ break;
+ }
+ }
+
/* Assign. */
$this->view->title = $title;
$this->view->position = $position;
@@ -963,6 +974,7 @@ class execution extends control
$this->view->canBeChanged = common::canModify('execution', $execution); // Determines whether an object is editable.
$this->view->showBranch = $showBranch;
$this->view->storyStages = $this->product->batchGetStoryStage($stories);
+ $this->view->multiBranch = $multiBranch;
$this->display();
}
@@ -1298,21 +1310,26 @@ class execution extends control
/* Set execution builds. */
$executionBuilds = array();
$productList = $this->product->getProducts($executionID);
- $this->app->loadLang('branch');
+ $showBranch = false;
if(!empty($builds))
{
+ foreach($builds as $build) $executionBuilds[$build->product][] = $build;
+
+ /* Get branch name. */
+ $branchGroups = $this->loadModel('branch')->getByProducts(array_keys($executionBuilds));
foreach($builds as $build)
{
- /* If product is normal, unset branch name. */
- if(isset($productList[$build->product]) and $productList[$build->product]->type == 'normal')
+ $build->branchName = '';
+ if(isset($branchGroups[$build->product]))
{
- $build->branchName = '';
+ $showBranch = true;
+ $branchPairs = $branchGroups[$build->product];
+ foreach(explode(',', trim($build->branch, ',')) as $branchID)
+ {
+ if(isset($branchPairs[$branchID])) $build->branchName .= "{$branchPairs[$branchID]},";
+ }
+ $build->branchName = trim($build->branchName, ',');
}
- else
- {
- $build->branchName = isset($build->branchName) ? $build->branchName : $this->lang->branch->main;
- }
- $executionBuilds[$build->product][] = $build;
}
}
@@ -1328,6 +1345,7 @@ class execution extends control
$this->view->product = $type == 'product' ? $param : 'all';
$this->view->products = $products;
$this->view->type = $type;
+ $this->view->showBranch = $showBranch;
$this->display();
}
@@ -1579,6 +1597,7 @@ class execution extends control
$execution = $this->commonAction($executionID);
$executionID = $execution->id;
+ $deptID = $this->app->user->admin ? 0 : $this->app->user->dept;
$title = $execution->name . $this->lang->colon . $this->lang->execution->team;
$position[] = html::a($this->createLink('execution', 'browse', "executionID=$executionID"), $execution->name);
@@ -1586,7 +1605,7 @@ class execution extends control
$this->view->title = $title;
$this->view->position = $position;
- $this->view->deptUsers = $this->loadModel('dept')->getDeptUserPairs($this->app->user->dept, 'id');
+ $this->view->deptUsers = $this->loadModel('dept')->getDeptUserPairs($deptID, 'id');
$this->view->canBeChanged = common::canModify('execution', $execution); // Determines whether an object is editable.
$this->display();
@@ -1645,7 +1664,20 @@ class execution extends control
}
else
{
- return print(js::confirm($this->lang->execution->importPlanStory, inlink('create', "projectID=$projectID&executionID=$executionID©ExecutionID=&planID=$planID&confirm=yes"), inlink('create', "projectID=$projectID&executionID=$executionID")));
+ $executionProductList = $this->loadModel('product')->getProducts($executionID);
+ $multiBranchProduct = false;
+ foreach($executionProductList as $executionProduct)
+ {
+ if($executionProduct->type != 'normal')
+ {
+ $multiBranchProduct = true;
+ break;
+ }
+ }
+
+ $importPlanStoryTips = $multiBranchProduct ? $this->lang->execution->importBranchPlanStory : $this->lang->execution->importPlanStory;
+
+ return print(js::confirm($importPlanStoryTips, inlink('create', "projectID=$projectID&executionID=$executionID©ExecutionID=&planID=$planID&confirm=yes"), inlink('create', "projectID=$projectID&executionID=$executionID")));
}
}
@@ -1689,10 +1721,11 @@ class execution extends control
$linkedBranches = array();
foreach($products as $productIndex => $product)
{
+ $productPlans[$productIndex] = array();
foreach($branches[$productIndex] as $branchID => $branch)
{
$linkedBranches[$productIndex][$branchID] = $branchID;
- $productPlans[$productIndex][$branchID] = isset($plans[$productIndex][$branchID]) ? $plans[$productIndex][$branchID] : array();
+ $productPlans[$productIndex] += isset($plans[$productIndex][$branchID]) ? $plans[$productIndex][$branchID] : array();
}
}
@@ -1724,10 +1757,11 @@ class execution extends control
$linkedBranches = array();
foreach($products as $productIndex => $product)
{
+ $productPlans[$productIndex] = array();
foreach($branches[$productIndex] as $branchID => $branch)
{
$linkedBranches[$productIndex][$branchID] = $branchID;
- $productPlans[$productIndex][$branchID] = isset($plans[$productIndex][$branchID]) ? $plans[$productIndex][$branchID] : array();
+ $productPlans[$productIndex] += isset($plans[$productIndex][$branchID]) ? $plans[$productIndex][$branchID] : array();
}
}
@@ -1776,27 +1810,15 @@ class execution extends control
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('doc', 'objectLibs', "type=execution")));
}
- $planID = '';
- if(isset($_POST['plans']))
- {
- foreach($_POST['plans'] as $plans)
- {
- foreach($plans as $planID)
- {
- if(!empty($planID)) break;
- }
- }
- }
-
if(!empty($projectID) and $project->model == 'kanban')
{
$execution = $this->execution->getById($executionID);
$this->loadModel('kanban')->createRDKanban($execution);
}
- if(!empty($planID))
+ if(!empty($_POST['plans']))
{
- return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('create', "projectID=$projectID&executionID=$executionID©ExecutionID=&planID=$planID&confirm=no")));
+ return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('create', "projectID=$projectID&executionID=$executionID©ExecutionID=&planID=1&confirm=no")));
}
else
{
@@ -1904,7 +1926,20 @@ class execution extends control
}
elseif(!empty($newPlans))
{
- return print(js::confirm($this->lang->execution->importEditPlanStory, inlink('edit', "executionID=$executionID&action=edit&extra=&newPlans=$newPlans&confirm=yes"), inlink('view', "executionID=$executionID")));
+ $executionProductList = $this->loadModel('product')->getProducts($executionID);
+ $multiBranchProduct = false;
+ foreach($executionProductList as $executionProduct)
+ {
+ if($executionProduct->type != 'normal')
+ {
+ $multiBranchProduct = true;
+ break;
+ }
+ }
+
+ $importEditPlanStoryTips = $multiBranchProduct ? $this->lang->execution->importBranchEditPlanStory : $this->lang->execution->importEditPlanStory;
+
+ return print(js::confirm($importEditPlanStoryTips, inlink('edit', "executionID=$executionID&action=edit&extra=&newPlans=$newPlans&confirm=yes"), inlink('view', "executionID=$executionID")));
}
/* Set menu. */
@@ -2003,12 +2038,15 @@ class execution extends control
foreach($linkedProducts as $productID => $linkedProduct)
{
if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->name;
+ $productPlans[$productID] = array();
+
foreach($branches[$productID] as $branchID => $branch)
{
+ $productPlans[$productID] += isset($plans[$productID][$branchID]) ? $plans[$productID][$branchID] : array();
+
$linkedBranchList[$branchID] = $branchID;
$linkedBranches[$productID][$branchID] = $branchID;
- $productPlans[$productID][$branchID] = isset($plans[$productID][$branchID]) ? $plans[$productID][$branchID] : array();
- if($branchID != BRANCH_MAIN and isset($plans[$productID][BRANCH_MAIN])) $productPlans[$productID][$branchID] += $plans[$productID][BRANCH_MAIN];
+ if($branchID != BRANCH_MAIN and isset($plans[$productID][BRANCH_MAIN])) $productPlans[$productID] += $plans[$productID][BRANCH_MAIN];
if(!empty($executionStories[$productID][$branchID]))
{
array_push($unmodifiableProducts, $productID);
@@ -4121,9 +4159,11 @@ class execution extends control
$count = 0;
if(!empty($planStory))
{
+ $projectProducts = $this->loadModel('project')->getBranchesByProject($executionID);
foreach($planStory as $id => $story)
{
- if($story->status == 'draft' or $story->status == 'reviewing')
+ $projectBranches = zget($projectProducts, $story->product, array());
+ if($story->status != 'active' or (!empty($story->branch) and !empty($projectBranches) and !isset($projectBranches[$story->branch])))
{
$count++;
unset($planStory[$id]);
@@ -4155,8 +4195,28 @@ class execution extends control
include $this->app->getModulePath('', 'execution') . 'lang/' . $this->app->getClientLang() . '.php';
}
- $haveDraft = sprintf($this->lang->execution->haveDraft, $count);
- if(!$execution->multiple) $haveDraft = str_replace($this->lang->executionCommon, $this->lang->projectCommon, $haveDraft);
+ $multiBranchProduct = false;
+ if($productID)
+ {
+ $product = $this->loadModel('product')->getByID($productID);
+ if($product->type != 'normal') $multiBranchProduct = true;
+ }
+ else
+ {
+ $executionProductList = $this->loadModel('product')->getProducts($executionID);
+ foreach($executionProductList as $executionProduct)
+ {
+ if($executionProduct->type != 'normal')
+ {
+ $multiBranchProduct = true;
+ break;
+ }
+ }
+ }
+ $importPlanStoryTips = $multiBranchProduct ? $this->lang->execution->haveBranchDraft : $this->lang->execution->haveDraft;
+
+ $haveDraft = sprintf($importPlanStoryTips, $count);
+ if(!$execution->multiple or $moduleName == 'projectstory') $haveDraft = str_replace($this->lang->executionCommon, $this->lang->projectCommon, $haveDraft);
if($count != 0) echo js::alert($haveDraft) . js::locate($this->createLink($moduleName, $fromMethod, $param));
return print(js::locate(helper::createLink($moduleName, $fromMethod, $param)));
}
diff --git a/module/execution/css/create.css b/module/execution/css/create.css
index 839355f188..a71b046e42 100644
--- a/module/execution/css/create.css
+++ b/module/execution/css/create.css
@@ -21,9 +21,9 @@
.chosen-container.chosen-highlight-selected .disabled-result.result-selected {background: none;}
-#productsBox .row .col-sm-4 {padding-right: 13px;}
-#plansBox .row {display: inline-table; width: 102%;}
-#plansBox .row .col-sm-4 {display: inline-block; float: none; padding-right: 13px;}
+.productsBox .row .col-sm-4 {padding-right: 13px;}
+.plansBox .row {display: inline-table; width: 102%;}
+.plansBox .row .col-sm-4 {display: inline-block; float: none; padding-right: 13px;}
#dateRange {vertical-align: top; padding-top: 13px;}
#dateRangeOption {vertical-align: top; padding-top: 13px;}
@@ -31,4 +31,10 @@
#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;}
+#copyProjectModal .projectSelect a.chosen-single > div > b {top: -4px !important;}
+
+.productsBox a {border-radius: 2px !important;}
+.productsBox .required:after {right: -9.5px !important;}
+.productsBox .input-group-addon > div {display: inline-block !important;}
+.productsBox .required + .text-danger.help-text {position: relative; left: 10px;}
+.productsBox > #productNameLabel {padding-top: 8px;}
diff --git a/module/execution/css/story.css b/module/execution/css/story.css
index e0333312b2..2eff8dd035 100644
--- a/module/execution/css/story.css
+++ b/module/execution/css/story.css
@@ -34,3 +34,4 @@ th.c-pri{width:50px;}
.table td.c-estimate {padding-right: 12px;}
.main-table tbody > tr.table-children > td:first-child::before {width: 3px;}
.table-footer {z-index: 1;}
+#linkStoryByPlan .modal-header {padding: 20px 0 10px 0; border-bottom: 0px}
\ No newline at end of file
diff --git a/module/execution/js/common.js b/module/execution/js/common.js
index 19d68b6372..b7a2eb2964 100644
--- a/module/execution/js/common.js
+++ b/module/execution/js/common.js
@@ -113,6 +113,15 @@ function computeEndDate(delta)
computeWorkDays();
}
+/* Auto compute the work days. */
+$(function()
+{
+ $(".date").bind('dateSelected', function()
+ {
+ computeWorkDays(this.id);
+ })
+});
+
/**
* Load branches.
*
@@ -122,118 +131,127 @@ function computeEndDate(delta)
*/
function loadBranches(product)
{
- if($('#productsBox .input-group:last select:first').val() != 0)
+ /* When selecting a product, delete a plan that is empty by default. */
+ $("#planDefault").remove();
+
+ $(".productsBox select[name^='products']").each(function()
{
- if(model !== 'waterfall')
+ var $product = $(product);
+ var productID = $(this).val();
+ if($product.val() != 0 && $product.val() == $(this).val() && $product.attr('id') != $(this).attr('id') && !multiBranchProducts[$product.val()])
{
- var length = $('#productsBox .input-group').size();
- $('#productsBox .row').append('' + $('#productsBox .col-sm-4:last').html().replace('required', '') + '
');
- if($('#productsBox .input-group:last select').size() >= 2) $('#productsBox .input-group:last select:last').remove();
- $('#productsBox .input-group:last .chosen-container').remove();
- $('#productsBox .input-group:last select:first').attr('name', 'products[' + length + ']').attr('id', 'products' + length);
- $('#productsBox .input-group:last .chosen').chosen();
+ bootbox.alert(errorSameProducts);
+ $product.val(0);
+ $product.trigger("chosen:updated");
+ return false;
}
- adjustProductBoxMargin();
+ });
+
+ var $tableRow = $(product).closest('.table-row');
+ var index = $tableRow.find('select:first').attr('id').replace('products' , '');
+ var oldBranch = $(product).attr('data-branch') !== undefined ? $(product).attr('data-branch') : 0;
+ if($(product).val() != 0)
+ {
+ $(product).closest('tr').find('.newProduct').addClass('hidden')
+ }
+ else
+ {
+ $(product).closest('tr').find('.newProduct').removeClass('hidden')
}
- var $inputgroup = $(product).closest('.input-group');
- if($inputgroup.find('select').size() >= 2) $inputgroup.removeClass('has-branch').find('select:last').remove();
- if($inputgroup.find('.chosen-container').size() >= 2) $inputgroup.find('.chosen-container:last').remove();
+ if(!multiBranchProducts[$(product).val()])
+ {
+ $tableRow.find('.table-col:last select').val('').trigger('chosen:updated');
+ $tableRow.find('.table-col:last').addClass('hidden');
+ }
- var projectID = $('#project').val();
- if(typeof(projectID) == 'undefined') projectID = 0;
-
- var index = $inputgroup.find('select:first').attr('id').replace('products' , '');
- $.get(createLink('branch', 'ajaxGetBranches', "productID=" + $(product).val() + "&oldBranch=0¶m=active&projectID=" + projectID + "&withMainBranch=true"), function(data)
+ $.get(createLink('branch', 'ajaxGetBranches', "productID=" + $(product).val() + "&oldBranch=" + oldBranch + "¶m=active&projectID=" + projectID + "&withMainBranch=true"), function(data)
{
if(data)
{
- $inputgroup.addClass('has-branch').append(data);
- $inputgroup.find('select:last').attr('name', 'branch[' + index + ']').attr('id', 'branch' + index).attr('onchange', "loadPlans('#products" + index + "', this.value)").chosen();
+ $tableRow.find("select[name^='branch']").replaceWith(data);
+ $tableRow.find('.table-col:last .chosen-container').remove();
+ $tableRow.find('.table-col:last').removeClass('hidden');
+ $tableRow.find("select[name^='branch']").attr('multiple', '').attr('name', 'branch[' + index + '][]').attr('id', 'branch' + index).attr('onchange', "loadPlans('#products" + index + "', this)").chosen();
- $inputgroup.find('select:last').each(disableSelectedBranch);
disableSelectedProduct();
}
- var branchID = $('#branch' + index).val();
- loadPlans(product, branchID);
+ var branch = $('#branch' + index);
+ loadPlans(product, branch);
});
-
- if(!multiBranchProducts[$(product).val()]) disableSelectedProduct();
}
/**
- * Load plans by product id.
+ * Load plans.
*
- * @param int $product
- * @param int $branchID
+ * @param obj $product
+ * @param obj $branchID
* @access public
* @return void
*/
-function loadPlans(product, branchID)
+function loadPlans(product, branch)
{
- if($('#plansBox').size() == 0) return false;
-
var productID = $(product).val();
- var branchID = typeof(branchID) == 'undefined' ? 0 : branchID;
+ var branchID = $(branch).val() == null ? 0 : '0,' + $(branch).val();
+ var planID = $(product).attr('data-plan') !== undefined ? $(product).attr('data-plan') : 0;
var index = $(product).attr('id').replace('products', '');
- $.get(createLink('product', 'ajaxGetPlans', "productID=" + productID + '&branch=0,' + branchID + '&planID=0&fieldID&needCreate=&expired=noclosed,unexpired¶m=skipParent,multiple'), function(data)
+ $.get(createLink('product', 'ajaxGetPlans', "productID=" + productID + '&branch=' + branchID + '&planID=' + planID + '&fieldID&needCreate=&expired=unexpired,noclosed¶m=skipParent,multiple'), function(data)
{
if(data)
{
- if($("div#plan" + index).size() == 0) $("#plansBox .row").append('
');
- $("div#plan" + index).html(data).find('select').attr('name', 'plans[' + productID + '][' + branchID + '][]').attr('id', 'plans' + productID).chosen();
-
- adjustPlanBoxMargin();
+ $("div#plan" + index).find("select[name^='plans']").replaceWith(data);
+ $("div#plan" + index).find('.chosen-container').remove();
+ $("div#plan" + index).find('select').attr('name', 'plans[' + productID + ']' + '[]').attr('id', 'plans' + productID).chosen();
}
});
}
/**
- * Adjust product box margin.
- *
+ * Add new line for link product.
+ *
+ * @param obj $obj
* @access public
* @return void
*/
-function adjustProductBoxMargin()
+function addNewLine(obj)
{
- var productRows = Math.ceil($('#productsBox > .row > .col-sm-4').length / 3);
- if(productRows > 1)
+ var newLine = $(obj).closest('tr').clone();
+ var index = 0;
+ $(".productsBox select[name^='products']").each(function()
{
- for(i = 1; i <= productRows - 1; i++)
- {
- $('#productsBox .col-sm-4:lt(' + (i * 3) + ')').css('margin-bottom', '10px');
- }
- }
-}
+ var id = $(this).attr('id').replace('products' , '');
-/**
- * Adjust plan box margin.
- *
- * @access public
- * @return void
- */
-function adjustPlanBoxMargin()
-{
- var planRows = Math.ceil($('#plansBox > .row > .col-sm-4').length / 3);
- if(planRows > 1)
- {
- for(j = 1; j <= planRows - 1; j++)
- {
- $('#plansBox .col-sm-4:lt(' + (j * 3) + ')').css('margin-bottom', '10px');
- }
- }
-}
+ id = parseInt(id);
+ id ++;
-/* Auto compute the work days. */
-$(function()
-{
- $(".date").bind('dateSelected', function()
- {
- computeWorkDays(this.id);
+ index = id > index ? id : index;
})
-});
+
+ newLine.find('.newProduct').remove();
+ newLine.find('.addProduct').remove();
+ newLine.addClass('newLine');
+ newLine.find('th').html('');
+ newLine.find('.removeLine').css('visibility', 'visible');
+ newLine.find('.chosen-container').remove();
+ newLine.find('.productsBox .table-col:last').addClass('hidden');
+ newLine.find("select[name^='products']").attr('name', 'products[' + index + ']').attr('id', 'products' + index).val('').chosen();
+ newLine.find("select[name^='plans']").attr('name', 'plans[' + index + '][' + 0 + '][]').chosen();
+ newLine.find("div[id^='plan']").attr('id', 'plan' + index);
+
+ $(obj).closest('tr').after(newLine);
+ var product = newLine.find("select[name^='products']");
+ var branch = newLine.find("select[name^='branch']");
+ loadPlans(product, branch);
+ disableSelectedProduct();
+}
+
+function removeLine(obj)
+{
+ $(obj).closest('tr').remove();
+ disableSelectedProduct();
+}
$(function()
{
@@ -247,9 +265,6 @@ $(function()
e.stopPropagation();
e.preventDefault();
});
-
- adjustProductBoxMargin();
- adjustPlanBoxMargin();
});
/**
diff --git a/module/execution/lang/de.php b/module/execution/lang/de.php
index 48973a57cf..1aad7582a6 100644
--- a/module/execution/lang/de.php
+++ b/module/execution/lang/de.php
@@ -209,82 +209,84 @@ $lang->execution->burnByList['estimate'] = "View by plan hours";
$lang->execution->burnByList['storyPoint'] = 'View by story point';
/* Method list. */
-$lang->execution->index = "Home";
-$lang->execution->task = 'Aufgaben';
-$lang->execution->groupTask = 'Nach Gruppen';
-$lang->execution->story = 'Storys';
-$lang->execution->qa = 'QA';
-$lang->execution->bug = 'Bugs';
-$lang->execution->testcase = 'Testcase List';
-$lang->execution->dynamic = 'Verlauf';
-$lang->execution->latestDynamic = 'Letzter Verlauf';
-$lang->execution->build = 'Builds';
-$lang->execution->testtask = 'Testaufgaben';
-$lang->execution->burn = 'Burndown';
-$lang->execution->computeBurn = 'Aktualisieren';
-$lang->execution->CFD = 'Cumulative Flow diagrams';
-$lang->execution->computeCFD = 'Compute Cumulative Flow diagrams';
-$lang->execution->burnData = 'Burndown Daten';
-$lang->execution->fixFirst = 'Bearbeite Mannstunden des ersten Tags';
-$lang->execution->team = 'Teammitglieder';
-$lang->execution->doc = 'Dok';
-$lang->execution->doclib = 'Dok Bibliothek';
-$lang->execution->manageProducts = 'Verküpfe ' . $lang->productCommon;
-$lang->execution->linkStory = 'Link Stories';
-$lang->execution->linkStoryByPlan = 'Verküpfe Story aus Plan';
-$lang->execution->linkPlan = 'Verküpfe Plan';
-$lang->execution->unlinkStoryTasks = 'Verknüpfung aufheben';
-$lang->execution->linkedProducts = 'Verküpfte Produkte';
-$lang->execution->unlinkedProducts = 'Produkt verknüpfung aufheben';
-$lang->execution->view = "Übersicht";
-$lang->execution->startAction = "Start Execution";
-$lang->execution->activateAction = "Activate Execution";
-$lang->execution->delayAction = "Delay Execution";
-$lang->execution->suspendAction = "Suspend Execution";
-$lang->execution->closeAction = "Close Execution";
-$lang->execution->testtaskAction = "Execution Request";
-$lang->execution->teamAction = "Execution Members";
-$lang->execution->kanbanAction = "Execution Kanban";
-$lang->execution->printKanbanAction = "Print Kanban";
-$lang->execution->treeAction = "Execution Tree View";
-$lang->execution->exportAction = "Export Execution";
-$lang->execution->computeBurnAction = "Compute Burn";
-$lang->execution->create = "Erstelle Projekt";
-$lang->execution->createExec = "Create Execution";
-$lang->execution->createAction = "Create {$lang->execution->common}";
-$lang->execution->copyExec = "Copy Execution";
-$lang->execution->copy = "Kopiere {$lang->executionCommon}";
-$lang->execution->delete = "Lösche";
-$lang->execution->deleteAB = "Lösche Execution";
-$lang->execution->browse = "Durchsuchen";
-$lang->execution->edit = "Bearbeiten";
-$lang->execution->editAction = "Edit Execution";
-$lang->execution->batchEdit = "Mehere bearbeiten";
-$lang->execution->batchEditAction = "Batch Edit";
-$lang->execution->manageMembers = 'Teams verwalten';
-$lang->execution->unlinkMember = 'Mitgliefer entfernen';
-$lang->execution->unlinkStory = 'Story entfernen';
-$lang->execution->unlinkStoryAB = 'Unlink';
-$lang->execution->batchUnlinkStory = 'Mehere Storys entfernen';
-$lang->execution->importTask = 'Importiere Aufgaben';
-$lang->execution->importPlanStories = 'Verknüpfe Story aus Plan';
-$lang->execution->importBug = 'Importiere Bugs';
-$lang->execution->tree = 'Baum';
-$lang->execution->treeTask = 'Aufgabe anzeigen';
-$lang->execution->treeStory = 'Story anzeigen';
-$lang->execution->treeViewTask = 'Tree View Task';
-$lang->execution->treeViewStory = 'Tree View Story';
-$lang->execution->storyKanban = 'Story Kanban';
-$lang->execution->storySort = 'Story sortieren';
-$lang->execution->importPlanStory = '' . $lang->executionCommon . ' wurde erstellt!\nMöchten Sie Storys aus dem Plan importieren?';
-$lang->execution->importEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
-$lang->execution->needLinkProducts = 'The execution has not been linked with any product, and the related functions cannot be used. Please link the product first and try again.';
-$lang->execution->iteration = 'Iteration';
-$lang->execution->iterationInfo = '%s Iterationen';
-$lang->execution->viewAll = 'Alle anzeigen';
-$lang->execution->testreport = 'Test Report';
-$lang->execution->taskKanban = 'Task Kanban';
-$lang->execution->RDKanban = 'Research & Development Kanban';
+$lang->execution->index = "Home";
+$lang->execution->task = 'Aufgaben';
+$lang->execution->groupTask = 'Nach Gruppen';
+$lang->execution->story = 'Storys';
+$lang->execution->qa = 'QA';
+$lang->execution->bug = 'Bugs';
+$lang->execution->testcase = 'Testcase List';
+$lang->execution->dynamic = 'Verlauf';
+$lang->execution->latestDynamic = 'Letzter Verlauf';
+$lang->execution->build = 'Builds';
+$lang->execution->testtask = 'Testaufgaben';
+$lang->execution->burn = 'Burndown';
+$lang->execution->computeBurn = 'Aktualisieren';
+$lang->execution->CFD = 'Cumulative Flow diagrams';
+$lang->execution->computeCFD = 'Compute Cumulative Flow diagrams';
+$lang->execution->burnData = 'Burndown Daten';
+$lang->execution->fixFirst = 'Bearbeite Mannstunden des ersten Tags';
+$lang->execution->team = 'Teammitglieder';
+$lang->execution->doc = 'Dok';
+$lang->execution->doclib = 'Dok Bibliothek';
+$lang->execution->manageProducts = 'Verküpfe ' . $lang->productCommon;
+$lang->execution->linkStory = 'Link Stories';
+$lang->execution->linkStoryByPlan = 'Verküpfe Story aus Plan';
+$lang->execution->linkPlan = 'Verküpfe Plan';
+$lang->execution->unlinkStoryTasks = 'Verknüpfung aufheben';
+$lang->execution->linkedProducts = 'Verküpfte Produkte';
+$lang->execution->unlinkedProducts = 'Produkt verknüpfung aufheben';
+$lang->execution->view = "Übersicht";
+$lang->execution->startAction = "Start Execution";
+$lang->execution->activateAction = "Activate Execution";
+$lang->execution->delayAction = "Delay Execution";
+$lang->execution->suspendAction = "Suspend Execution";
+$lang->execution->closeAction = "Close Execution";
+$lang->execution->testtaskAction = "Execution Request";
+$lang->execution->teamAction = "Execution Members";
+$lang->execution->kanbanAction = "Execution Kanban";
+$lang->execution->printKanbanAction = "Print Kanban";
+$lang->execution->treeAction = "Execution Tree View";
+$lang->execution->exportAction = "Export Execution";
+$lang->execution->computeBurnAction = "Compute Burn";
+$lang->execution->create = "Erstelle Projekt";
+$lang->execution->createExec = "Create Execution";
+$lang->execution->createAction = "Create {$lang->execution->common}";
+$lang->execution->copyExec = "Copy Execution";
+$lang->execution->copy = "Kopiere {$lang->executionCommon}";
+$lang->execution->delete = "Lösche";
+$lang->execution->deleteAB = "Lösche Execution";
+$lang->execution->browse = "Durchsuchen";
+$lang->execution->edit = "Bearbeiten";
+$lang->execution->editAction = "Edit Execution";
+$lang->execution->batchEdit = "Mehere bearbeiten";
+$lang->execution->batchEditAction = "Batch Edit";
+$lang->execution->manageMembers = 'Teams verwalten';
+$lang->execution->unlinkMember = 'Mitgliefer entfernen';
+$lang->execution->unlinkStory = 'Story entfernen';
+$lang->execution->unlinkStoryAB = 'Unlink';
+$lang->execution->batchUnlinkStory = 'Mehere Storys entfernen';
+$lang->execution->importTask = 'Importiere Aufgaben';
+$lang->execution->importPlanStories = 'Verknüpfe Story aus Plan';
+$lang->execution->importBug = 'Importiere Bugs';
+$lang->execution->tree = 'Baum';
+$lang->execution->treeTask = 'Aufgabe anzeigen';
+$lang->execution->treeStory = 'Story anzeigen';
+$lang->execution->treeViewTask = 'Tree View Task';
+$lang->execution->treeViewStory = 'Tree View Story';
+$lang->execution->storyKanban = 'Story Kanban';
+$lang->execution->storySort = 'Story sortieren';
+$lang->execution->importPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
+$lang->execution->importEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
+$lang->execution->importBranchPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? Only the activation stories of the branch associated with this ' .$lang->executionCommon. ' will be associated with the import';
+$lang->execution->importBranchEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? Only the activation stories of the branch associated with this ' .$lang->executionCommon. ' will be associated with the import';
+$lang->execution->needLinkProducts = 'The execution has not been linked with any product, and the related functions cannot be used. Please link the product first and try again.';
+$lang->execution->iteration = 'Iteration';
+$lang->execution->iterationInfo = '%s Iterationen';
+$lang->execution->viewAll = 'Alle anzeigen';
+$lang->execution->testreport = 'Test Report';
+$lang->execution->taskKanban = 'Task Kanban';
+$lang->execution->RDKanban = 'Research & Development Kanban';
/* Group browsing. */
$lang->execution->allTasks = 'Alle';
@@ -349,7 +351,8 @@ $lang->execution->howToUpdateBurn = " Story verknüpfen 。";
-$lang->execution->haveDraft = "There are %s draft stories can't be linked.";
+$lang->execution->haveBranchDraft = "There are %s draft stories or not associated with this {$lang->executionCommon} can't be linked.";
+$lang->execution->haveDraft = "There are %s draft stories with this {$lang->executionCommon} can't be linked.";
$lang->execution->doneExecutions = 'Erledigt';
$lang->execution->selectDept = 'Abteilung wählen';
$lang->execution->selectDeptTitle = 'Abteilung wählen';
@@ -594,3 +597,6 @@ $lang->execution->gantt->exporting = 'Exporting...';
$lang->execution->gantt->exportFail = 'Failed to export.';
$lang->execution->boardColorList = array('#32C5FF', '#006AF1', '#9D28B2', '#FF8F26', '#7FBB00', '#424BAC', '#66c5f8', '#EC2761');
+
+$lang->execution->linkBranchStoryByPlanTips = "When a scheduled association requirement is executed, only the active requirements associated with the %s of this execution are imported.";
+$lang->execution->linkNormalStoryByPlanTips = "Only the active requirements are imported when the scheduled requirements are associated.";
\ No newline at end of file
diff --git a/module/execution/lang/en.php b/module/execution/lang/en.php
index 45d52cf3bc..412c0c9f74 100644
--- a/module/execution/lang/en.php
+++ b/module/execution/lang/en.php
@@ -209,82 +209,84 @@ $lang->execution->burnByList['estimate'] = "View by plan hours";
$lang->execution->burnByList['storyPoint'] = 'View by story point';
/* Method list. */
-$lang->execution->index = "{$lang->executionCommon} Home";
-$lang->execution->task = 'Task List';
-$lang->execution->groupTask = 'Group View';
-$lang->execution->story = 'Story List';
-$lang->execution->qa = 'QA';
-$lang->execution->bug = 'Bug List';
-$lang->execution->testcase = 'Testcase List';
-$lang->execution->dynamic = 'Dynamics';
-$lang->execution->latestDynamic = 'Dynamics';
-$lang->execution->build = 'Build List';
-$lang->execution->testtask = 'Request';
-$lang->execution->burn = 'Burndown';
-$lang->execution->computeBurn = 'Update';
-$lang->execution->CFD = 'Cumulative Flow diagrams';
-$lang->execution->computeCFD = 'Compute Cumulative Flow diagrams';
-$lang->execution->burnData = 'Burndown Data';
-$lang->execution->fixFirst = 'Edit 1st-Day Estimates';
-$lang->execution->team = 'Members';
-$lang->execution->doc = 'Document';
-$lang->execution->doclib = 'Docoment Library';
-$lang->execution->manageProducts = 'Linked ' . $lang->productCommon . 's';
-$lang->execution->linkStory = 'Link Stories';
-$lang->execution->linkStoryByPlan = 'Link Stories By Plan';
-$lang->execution->linkPlan = 'Linked Plan';
-$lang->execution->unlinkStoryTasks = 'Unlink';
-$lang->execution->linkedProducts = "Linked {$lang->productCommon}s";
-$lang->execution->unlinkedProducts = "Unlinked {$lang->productCommon}s";
-$lang->execution->view = "Execution Detail";
-$lang->execution->startAction = "Start Execution";
-$lang->execution->activateAction = "Activate Execution";
-$lang->execution->delayAction = "Delay Execution";
-$lang->execution->suspendAction = "Suspend Execution";
-$lang->execution->closeAction = "Close Execution";
-$lang->execution->testtaskAction = "Execution Request";
-$lang->execution->teamAction = "Execution Members";
-$lang->execution->kanbanAction = "Execution Kanban";
-$lang->execution->printKanbanAction = "Print Kanban";
-$lang->execution->treeAction = "Execution Tree View";
-$lang->execution->exportAction = "Export Execution";
-$lang->execution->computeBurnAction = "Update Burndown";
-$lang->execution->create = "Create {$lang->executionCommon}";
-$lang->execution->createExec = "Create {$lang->execution->common}";
-$lang->execution->createAction = "Create {$lang->execution->common}";
-$lang->execution->copyExec = "Copy {$lang->execution->common}";
-$lang->execution->copy = "Copy {$lang->executionCommon}";
-$lang->execution->delete = "Delete {$lang->executionCommon}";
-$lang->execution->deleteAB = "Delete Execution";
-$lang->execution->browse = "{$lang->executionCommon} List";
-$lang->execution->edit = "Edit {$lang->executionCommon}";
-$lang->execution->editAction = "Edit Execution";
-$lang->execution->batchEdit = "Edit";
-$lang->execution->batchEditAction = "Batch Edit";
-$lang->execution->manageMembers = 'Manage Team';
-$lang->execution->unlinkMember = 'Remove Member';
-$lang->execution->unlinkStory = 'Unlink Story';
-$lang->execution->unlinkStoryAB = 'Unlink';
-$lang->execution->batchUnlinkStory = 'Batch Unlink Stories';
-$lang->execution->importTask = 'Transfer Task';
-$lang->execution->importPlanStories = 'Link Stories By Plan';
-$lang->execution->importBug = 'Import Bug';
-$lang->execution->tree = 'Tree';
-$lang->execution->treeTask = 'Show Task Only';
-$lang->execution->treeStory = 'Show Story Only';
-$lang->execution->treeViewTask = 'Tree View Task';
-$lang->execution->treeViewStory = 'Tree View Story';
-$lang->execution->storyKanban = 'Story Kanban';
-$lang->execution->storySort = 'Rank Story';
-$lang->execution->importPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
-$lang->execution->importEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
-$lang->execution->needLinkProducts = 'The execution has not been linked with any product, and the related functions cannot be used. Please link the product first and try again.';
-$lang->execution->iteration = 'Iterations';
-$lang->execution->iterationInfo = '%s Iterations';
-$lang->execution->viewAll = 'View All';
-$lang->execution->testreport = 'Test Report';
-$lang->execution->taskKanban = 'Task Kanban';
-$lang->execution->RDKanban = 'Research & Development Kanban';
+$lang->execution->index = "{$lang->executionCommon} Home";
+$lang->execution->task = 'Task List';
+$lang->execution->groupTask = 'Group View';
+$lang->execution->story = 'Story List';
+$lang->execution->qa = 'QA';
+$lang->execution->bug = 'Bug List';
+$lang->execution->testcase = 'Testcase List';
+$lang->execution->dynamic = 'Dynamics';
+$lang->execution->latestDynamic = 'Dynamics';
+$lang->execution->build = 'Build List';
+$lang->execution->testtask = 'Request';
+$lang->execution->burn = 'Burndown';
+$lang->execution->computeBurn = 'Update';
+$lang->execution->CFD = 'Cumulative Flow diagrams';
+$lang->execution->computeCFD = 'Compute Cumulative Flow diagrams';
+$lang->execution->burnData = 'Burndown Data';
+$lang->execution->fixFirst = 'Edit 1st-Day Estimates';
+$lang->execution->team = 'Members';
+$lang->execution->doc = 'Document';
+$lang->execution->doclib = 'Docoment Library';
+$lang->execution->manageProducts = 'Linked ' . $lang->productCommon . 's';
+$lang->execution->linkStory = 'Link Stories';
+$lang->execution->linkStoryByPlan = 'Link Stories By Plan';
+$lang->execution->linkPlan = 'Linked Plan';
+$lang->execution->unlinkStoryTasks = 'Unlink';
+$lang->execution->linkedProducts = "Linked {$lang->productCommon}s";
+$lang->execution->unlinkedProducts = "Unlinked {$lang->productCommon}s";
+$lang->execution->view = "Execution Detail";
+$lang->execution->startAction = "Start Execution";
+$lang->execution->activateAction = "Activate Execution";
+$lang->execution->delayAction = "Delay Execution";
+$lang->execution->suspendAction = "Suspend Execution";
+$lang->execution->closeAction = "Close Execution";
+$lang->execution->testtaskAction = "Execution Request";
+$lang->execution->teamAction = "Execution Members";
+$lang->execution->kanbanAction = "Execution Kanban";
+$lang->execution->printKanbanAction = "Print Kanban";
+$lang->execution->treeAction = "Execution Tree View";
+$lang->execution->exportAction = "Export Execution";
+$lang->execution->computeBurnAction = "Update Burndown";
+$lang->execution->create = "Create {$lang->executionCommon}";
+$lang->execution->createExec = "Create {$lang->execution->common}";
+$lang->execution->createAction = "Create {$lang->execution->common}";
+$lang->execution->copyExec = "Copy {$lang->execution->common}";
+$lang->execution->copy = "Copy {$lang->executionCommon}";
+$lang->execution->delete = "Delete {$lang->executionCommon}";
+$lang->execution->deleteAB = "Delete Execution";
+$lang->execution->browse = "{$lang->executionCommon} List";
+$lang->execution->edit = "Edit {$lang->executionCommon}";
+$lang->execution->editAction = "Edit Execution";
+$lang->execution->batchEdit = "Edit";
+$lang->execution->batchEditAction = "Batch Edit";
+$lang->execution->manageMembers = 'Manage Team';
+$lang->execution->unlinkMember = 'Remove Member';
+$lang->execution->unlinkStory = 'Unlink Story';
+$lang->execution->unlinkStoryAB = 'Unlink';
+$lang->execution->batchUnlinkStory = 'Batch Unlink Stories';
+$lang->execution->importTask = 'Transfer Task';
+$lang->execution->importPlanStories = 'Link Stories By Plan';
+$lang->execution->importBug = 'Import Bug';
+$lang->execution->tree = 'Tree';
+$lang->execution->treeTask = 'Show Task Only';
+$lang->execution->treeStory = 'Show Story Only';
+$lang->execution->treeViewTask = 'Tree View Task';
+$lang->execution->treeViewStory = 'Tree View Story';
+$lang->execution->storyKanban = 'Story Kanban';
+$lang->execution->storySort = 'Rank Story';
+$lang->execution->importPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
+$lang->execution->importEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
+$lang->execution->importBranchPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? Only the activation stories of the branch associated with this ' .$lang->executionCommon. ' will be associated with the import';
+$lang->execution->importBranchEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? Only the activation stories of the branch associated with this ' .$lang->executionCommon. ' will be associated with the import';
+$lang->execution->needLinkProducts = 'The execution has not been linked with any product, and the related functions cannot be used. Please link the product first and try again.';
+$lang->execution->iteration = 'Iterations';
+$lang->execution->iterationInfo = '%s Iterations';
+$lang->execution->viewAll = 'View All';
+$lang->execution->testreport = 'Test Report';
+$lang->execution->taskKanban = 'Task Kanban';
+$lang->execution->RDKanban = 'Research & Development Kanban';
/* Group browsing. */
$lang->execution->allTasks = 'All';
@@ -349,7 +351,8 @@ $lang->execution->howToUpdateBurn = " Link Stories now.";
-$lang->execution->haveDraft = "%s stories in draft, so they can't be linked.";
+$lang->execution->haveBranchDraft = "There are %s draft stories or not associated with this {$lang->executionCommon} can't be linked.";
+$lang->execution->haveDraft = "There are %s draft stories with this {$lang->executionCommon} can't be linked.";
$lang->execution->doneExecutions = 'Finished';
$lang->execution->selectDept = 'Select Department';
$lang->execution->selectDeptTitle = 'Select User';
@@ -594,3 +597,6 @@ $lang->execution->gantt->exporting = 'Exporting...';
$lang->execution->gantt->exportFail = 'Failed to export.';
$lang->execution->boardColorList = array('#32C5FF', '#006AF1', '#9D28B2', '#FF8F26', '#7FBB00', '#424BAC', '#66c5f8', '#EC2761');
+
+$lang->execution->linkBranchStoryByPlanTips = "When a scheduled association requirement is executed, only the active requirements associated with the %s of this execution are imported.";
+$lang->execution->linkNormalStoryByPlanTips = "Only the active requirements are imported when the scheduled requirements are associated.";
\ No newline at end of file
diff --git a/module/execution/lang/fr.php b/module/execution/lang/fr.php
index 5b39ac9731..4e87634ef9 100644
--- a/module/execution/lang/fr.php
+++ b/module/execution/lang/fr.php
@@ -209,82 +209,84 @@ $lang->execution->burnByList['estimate'] = "View by plan hours";
$lang->execution->burnByList['storyPoint'] = 'View by story point';
/* Method list. */
-$lang->execution->index = "Accueil {$lang->executionCommon}";
-$lang->execution->task = 'Liste Tâches';
-$lang->execution->groupTask = 'Vision Groupée';
-$lang->execution->story = 'Liste Stories';
-$lang->execution->qa = 'QA';
-$lang->execution->bug = 'Liste Bugs';
-$lang->execution->testcase = 'Testcase List';
-$lang->execution->dynamic = 'Historique';
-$lang->execution->latestDynamic = 'Historique';
-$lang->execution->build = 'Liste Builds';
-$lang->execution->testtask = 'Recette';
-$lang->execution->burn = ' Atterrissage';
-$lang->execution->computeBurn = 'Calculer';
-$lang->execution->CFD = 'Cumulative Flow diagrams';
-$lang->execution->computeCFD = 'Compute Cumulative Flow diagrams';
-$lang->execution->burnData = "Données d'atterrissage";
-$lang->execution->fixFirst = 'Fixer 1er-Jour Estimation';
-$lang->execution->team = 'Membres';
-$lang->execution->doc = 'Documents';
-$lang->execution->doclib = 'Répertoire de Documents';
-$lang->execution->manageProducts = 'Liaisons du ' . $lang->executionCommon . ' avec les ' . $lang->productCommon . 's';
-$lang->execution->linkStory = 'Stories liées';
-$lang->execution->linkStoryByPlan = 'Stories liées par Plan';
-$lang->execution->linkPlan = 'Plans liés';
-$lang->execution->unlinkStoryTasks = 'Dissocier';
-$lang->execution->linkedProducts = "{$lang->productCommon}s liés à ce {$lang->executionCommon}";
-$lang->execution->unlinkedProducts = "{$lang->productCommon}s dissociés de ce {$lang->executionCommon}";
-$lang->execution->view = "Détail du Execution";
-$lang->execution->startAction = "Start Execution";
-$lang->execution->activateAction = "Activer le Execution";
-$lang->execution->delayAction = "Ajourner le Execution";
-$lang->execution->suspendAction = "Suspendre le Execution";
-$lang->execution->closeAction = "Fermer le Execution";
-$lang->execution->testtaskAction = "Recettes du Execution";
-$lang->execution->teamAction = "Membres du Execution";
-$lang->execution->kanbanAction = "Kaban Execution";
-$lang->execution->printKanbanAction = "Imprimer le Kanban";
-$lang->execution->treeAction = "Arborescence Execution";
-$lang->execution->exportAction = "Exporter Execution";
-$lang->execution->computeBurnAction = "Calculer Atterrissage";
-$lang->execution->create = "Créer {$lang->executionCommon}";
-$lang->execution->createExec = "Create Execution";
-$lang->execution->createAction = "Create {$lang->execution->common}";
-$lang->execution->copyExec = "Copy Execution";
-$lang->execution->copy = "Copier {$lang->executionCommon}";
-$lang->execution->delete = "Supprimer {$lang->executionCommon}";
-$lang->execution->deleteAB = "Delete Execution";
-$lang->execution->browse = "Liste du {$lang->executionCommon}";
-$lang->execution->edit = "Editer {$lang->executionCommon}";
-$lang->execution->editAction = "Edit Execution";
-$lang->execution->batchEdit = "Edition par lot";
-$lang->execution->batchEditAction = "Batch Edit";
-$lang->execution->manageMembers = 'Organiser Equipe';
-$lang->execution->unlinkMember = 'Retirer le membre';
-$lang->execution->unlinkStory = 'Dissocier Story';
-$lang->execution->unlinkStoryAB = 'Dissocier';
-$lang->execution->batchUnlinkStory = 'Dissocier Stories par lot';
-$lang->execution->importTask = 'Transfert Tâche';
-$lang->execution->importPlanStories = 'Lier Stories Par Plan';
-$lang->execution->importBug = 'Importer Bug';
-$lang->execution->tree = 'Arboressence';
-$lang->execution->treeTask = 'Seulement les Tâches';
-$lang->execution->treeStory = 'Seulement les Stories';
-$lang->execution->treeViewTask = 'Seulement les Tâches';
-$lang->execution->treeViewStory = 'Seulement les Stories';
-$lang->execution->storyKanban = 'Story Kanban';
-$lang->execution->storySort = 'Rang Story';
-$lang->execution->importPlanStory = $lang->executionCommon . ' est créé!\nVoulez-vous importer des stories qui ont été ajoutées au Plan ?';
-$lang->execution->importEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
-$lang->execution->needLinkProducts = 'The execution has not been linked with any product, and the related functions cannot be used. Please link the product first and try again.';
-$lang->execution->iteration = 'Itérations';
-$lang->execution->iterationInfo = '%s Itérations';
-$lang->execution->viewAll = 'Voir Tout';
-$lang->execution->testreport = 'Test Report';
-$lang->execution->taskKanban = 'Task Kanban';
-$lang->execution->RDKanban = 'Research & Development Kanban';
+$lang->execution->index = "{$lang->executionCommon} Home";
+$lang->execution->task = 'Task List';
+$lang->execution->groupTask = 'Group View';
+$lang->execution->story = 'Story List';
+$lang->execution->qa = 'QA';
+$lang->execution->bug = 'Bug List';
+$lang->execution->testcase = 'Testcase List';
+$lang->execution->dynamic = 'Dynamics';
+$lang->execution->latestDynamic = 'Dynamics';
+$lang->execution->build = 'Build List';
+$lang->execution->testtask = 'Request';
+$lang->execution->burn = 'Burndown';
+$lang->execution->computeBurn = 'Update';
+$lang->execution->CFD = 'Cumulative Flow diagrams';
+$lang->execution->computeCFD = 'Compute Cumulative Flow diagrams';
+$lang->execution->burnData = 'Burndown Data';
+$lang->execution->fixFirst = 'Edit 1st-Day Estimates';
+$lang->execution->team = 'Members';
+$lang->execution->doc = 'Document';
+$lang->execution->doclib = 'Docoment Library';
+$lang->execution->manageProducts = 'Linked ' . $lang->productCommon . 's';
+$lang->execution->linkStory = 'Link Stories';
+$lang->execution->linkStoryByPlan = 'Link Stories By Plan';
+$lang->execution->linkPlan = 'Linked Plan';
+$lang->execution->unlinkStoryTasks = 'Unlink';
+$lang->execution->linkedProducts = "Linked {$lang->productCommon}s";
+$lang->execution->unlinkedProducts = "Unlinked {$lang->productCommon}s";
+$lang->execution->view = "Execution Detail";
+$lang->execution->startAction = "Start Execution";
+$lang->execution->activateAction = "Activate Execution";
+$lang->execution->delayAction = "Delay Execution";
+$lang->execution->suspendAction = "Suspend Execution";
+$lang->execution->closeAction = "Close Execution";
+$lang->execution->testtaskAction = "Execution Request";
+$lang->execution->teamAction = "Execution Members";
+$lang->execution->kanbanAction = "Execution Kanban";
+$lang->execution->printKanbanAction = "Print Kanban";
+$lang->execution->treeAction = "Execution Tree View";
+$lang->execution->exportAction = "Export Execution";
+$lang->execution->computeBurnAction = "Update Burndown";
+$lang->execution->create = "Create {$lang->executionCommon}";
+$lang->execution->createExec = "Create {$lang->execution->common}";
+$lang->execution->createAction = "Create {$lang->execution->common}";
+$lang->execution->copyExec = "Copy {$lang->execution->common}";
+$lang->execution->copy = "Copy {$lang->executionCommon}";
+$lang->execution->delete = "Delete {$lang->executionCommon}";
+$lang->execution->deleteAB = "Delete Execution";
+$lang->execution->browse = "{$lang->executionCommon} List";
+$lang->execution->edit = "Edit {$lang->executionCommon}";
+$lang->execution->editAction = "Edit Execution";
+$lang->execution->batchEdit = "Edit";
+$lang->execution->batchEditAction = "Batch Edit";
+$lang->execution->manageMembers = 'Manage Team';
+$lang->execution->unlinkMember = 'Remove Member';
+$lang->execution->unlinkStory = 'Unlink Story';
+$lang->execution->unlinkStoryAB = 'Unlink';
+$lang->execution->batchUnlinkStory = 'Batch Unlink Stories';
+$lang->execution->importTask = 'Transfer Task';
+$lang->execution->importPlanStories = 'Link Stories By Plan';
+$lang->execution->importBug = 'Import Bug';
+$lang->execution->tree = 'Tree';
+$lang->execution->treeTask = 'Show Task Only';
+$lang->execution->treeStory = 'Show Story Only';
+$lang->execution->treeViewTask = 'Tree View Task';
+$lang->execution->treeViewStory = 'Tree View Story';
+$lang->execution->storyKanban = 'Story Kanban';
+$lang->execution->storySort = 'Rank Story';
+$lang->execution->importPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
+$lang->execution->importEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? The stories in the draft will be automatically filtered out when imported.';
+$lang->execution->importBranchPlanStory = $lang->executionCommon . ' is created!\nDo you want to import stories that have been linked to the plan? Only the activation stories of the branch associated with this ' .$lang->executionCommon. ' will be associated with the import';
+$lang->execution->importBranchEditPlanStory = $lang->executionCommon . ' is edited!\nDo you want to import stories that have been linked to the plan? Only the activation stories of the branch associated with this ' .$lang->executionCommon. ' will be associated with the import';
+$lang->execution->needLinkProducts = 'The execution has not been linked with any product, and the related functions cannot be used. Please link the product first and try again.';
+$lang->execution->iteration = 'Iterations';
+$lang->execution->iterationInfo = '%s Iterations';
+$lang->execution->viewAll = 'View All';
+$lang->execution->testreport = 'Test Report';
+$lang->execution->taskKanban = 'Task Kanban';
+$lang->execution->RDKanban = 'Research & Development Kanban';
/* Group browsing. */
$lang->execution->allTasks = 'Toutes';
@@ -349,7 +351,8 @@ $lang->execution->howToUpdateBurn = "executionCommon} qui sont associées à {$lang->productCommon} et vérifiez qu'elles ont bien été validées.";
$lang->execution->projectNoStories = "No story can be linked. Please check whether there is any story in project and make sure it has been reviewed.";
$lang->execution->productStories = "Les stories associées au {$lang->executionCommon} sont une portion des stories associées au {$lang->productCommon}. Les stories ne peuvent être associées à un {$lang->executionCommon} qu'après avoir été validées. Associer Stories maintenant.";
-$lang->execution->haveDraft = "%s stories sont encore en conception, elles ne peuvent pas être associées au {$lang->executionCommon} actuellement.";
+$lang->execution->haveBranchDraft = "There are %s draft stories or not associated with this {$lang->executionCommon} can't be linked.";
+$lang->execution->haveDraft = "There are %s draft stories with this {$lang->executionCommon} can't be linked.";
$lang->execution->doneExecutions = 'Terminé';
$lang->execution->selectDept = 'Sélection Compartiment';
$lang->execution->selectDeptTitle = 'Sélection Utilisateur';
@@ -594,3 +597,6 @@ $lang->execution->gantt->exporting = 'Exporting...';
$lang->execution->gantt->exportFail = 'Failed to export.';
$lang->execution->boardColorList = array('#32C5FF', '#006AF1', '#9D28B2', '#FF8F26', '#7FBB00', '#424BAC', '#66c5f8', '#EC2761');
+
+$lang->execution->linkBranchStoryByPlanTips = "When a scheduled association requirement is executed, only the active requirements associated with the %s of this execution are imported.";
+$lang->execution->linkNormalStoryByPlanTips = "Only the active requirements are imported when the scheduled requirements are associated.";
\ No newline at end of file
diff --git a/module/execution/lang/zh-cn.php b/module/execution/lang/zh-cn.php
index 3ff779523e..320385aef4 100644
--- a/module/execution/lang/zh-cn.php
+++ b/module/execution/lang/zh-cn.php
@@ -209,82 +209,84 @@ $lang->execution->burnByList['estimate'] = "按计划工时查看";
$lang->execution->burnByList['storyPoint'] = '按故事点查看';
/* 方法列表。*/
-$lang->execution->index = "{$lang->execution->common}主页";
-$lang->execution->task = '任务列表';
-$lang->execution->groupTask = '分组浏览任务';
-$lang->execution->story = "{$lang->SRCommon}列表";
-$lang->execution->qa = '测试仪表盘';
-$lang->execution->bug = 'Bug列表';
-$lang->execution->testcase = '用例列表';
-$lang->execution->dynamic = '动态';
-$lang->execution->latestDynamic = '最新动态';
-$lang->execution->build = '所有版本';
-$lang->execution->testtask = '测试单';
-$lang->execution->burn = '燃尽图';
-$lang->execution->computeBurn = '更新燃尽图';
-$lang->execution->CFD = '累积流图';
-$lang->execution->computeCFD = '更新累积流图';
-$lang->execution->burnData = '燃尽图数据';
-$lang->execution->fixFirst = '修改首天工时';
-$lang->execution->team = '团队成员';
-$lang->execution->doc = '文档列表';
-$lang->execution->doclib = '文档库列表';
-$lang->execution->manageProducts = '关联' . $lang->productCommon;
-$lang->execution->linkStory = "关联{$lang->SRCommon}";
-$lang->execution->linkStoryByPlan = "按照计划关联";
-$lang->execution->linkPlan = "关联计划";
-$lang->execution->unlinkStoryTasks = "未关联{$lang->SRCommon}任务";
-$lang->execution->linkedProducts = '已关联';
-$lang->execution->unlinkedProducts = '未关联';
-$lang->execution->view = "{$lang->execution->common}概况";
-$lang->execution->startAction = "开始{$lang->execution->common}";
-$lang->execution->activateAction = "激活{$lang->execution->common}";
-$lang->execution->delayAction = "延期{$lang->execution->common}";
-$lang->execution->suspendAction = "挂起{$lang->execution->common}";
-$lang->execution->closeAction = "关闭{$lang->execution->common}";
-$lang->execution->testtaskAction = "{$lang->execution->common}测试单";
-$lang->execution->teamAction = "{$lang->execution->common}团队";
-$lang->execution->kanbanAction = "{$lang->execution->common}看板";
-$lang->execution->printKanbanAction = "打印看板";
-$lang->execution->treeAction = "{$lang->execution->common}树状图";
-$lang->execution->exportAction = "导出{$lang->execution->common}";
-$lang->execution->computeBurnAction = "计算燃尽图";
-$lang->execution->create = "添加{$lang->executionCommon}";
-$lang->execution->createExec = "添加{$lang->execution->common}";
-$lang->execution->createAction = "添加{$lang->execution->common}";
-$lang->execution->copyExec = "复制{$lang->execution->common}";
-$lang->execution->copy = "复制{$lang->executionCommon}";
-$lang->execution->delete = "删除{$lang->executionCommon}";
-$lang->execution->deleteAB = "删除{$lang->execution->common}";
-$lang->execution->browse = "浏览{$lang->execution->common}";
-$lang->execution->edit = "设置{$lang->executionCommon}";
-$lang->execution->editAction = "编辑{$lang->execution->common}";
-$lang->execution->batchEdit = "编辑";
-$lang->execution->batchEditAction = "批量编辑";
-$lang->execution->manageMembers = '团队管理';
-$lang->execution->unlinkMember = '移除成员';
-$lang->execution->unlinkStory = "移除{$lang->SRCommon}";
-$lang->execution->unlinkStoryAB = "移除{$lang->SRCommon}";
-$lang->execution->batchUnlinkStory = "批量移除{$lang->SRCommon}";
-$lang->execution->importTask = '转入任务';
-$lang->execution->importPlanStories = "按计划关联{$lang->SRCommon}";
-$lang->execution->importBug = '导入Bug';
-$lang->execution->tree = '树状图';
-$lang->execution->treeTask = '只看任务';
-$lang->execution->treeStory = "只看{$lang->SRCommon}";
-$lang->execution->treeViewTask = '树状图查看任务';
-$lang->execution->treeViewStory = "树状图查看{$lang->SRCommon}";
-$lang->execution->storyKanban = "{$lang->SRCommon}看板";
-$lang->execution->storySort = "{$lang->SRCommon}排序";
-$lang->execution->importPlanStory = '创建' . $lang->executionCommon . '成功!\n是否导入计划关联的相关' . $lang->SRCommon . '?导入时将自动过滤掉草稿状态的' . $lang->SRCommon . '。';
-$lang->execution->importEditPlanStory = '编辑' . $lang->executionCommon . '成功!\n是否导入计划关联的相关' . $lang->SRCommon . '?导入时将自动过滤掉草稿状态的' . $lang->SRCommon . '。';
-$lang->execution->needLinkProducts = '该执行还未关联任何产品,相关功能无法使用,请先关联产品后再试。';
-$lang->execution->iteration = '版本迭代';
-$lang->execution->iterationInfo = '迭代%s次';
-$lang->execution->viewAll = '查看所有';
-$lang->execution->testreport = '测试报告';
-$lang->execution->taskKanban = '任务看板';
-$lang->execution->RDKanban = '研发看板';
+$lang->execution->index = "{$lang->execution->common}主页";
+$lang->execution->task = '任务列表';
+$lang->execution->groupTask = '分组浏览任务';
+$lang->execution->story = "{$lang->SRCommon}列表";
+$lang->execution->qa = '测试仪表盘';
+$lang->execution->bug = 'Bug列表';
+$lang->execution->testcase = '用例列表';
+$lang->execution->dynamic = '动态';
+$lang->execution->latestDynamic = '最新动态';
+$lang->execution->build = '所有版本';
+$lang->execution->testtask = '测试单';
+$lang->execution->burn = '燃尽图';
+$lang->execution->computeBurn = '更新燃尽图';
+$lang->execution->CFD = '累积流图';
+$lang->execution->computeCFD = '更新累积流图';
+$lang->execution->burnData = '燃尽图数据';
+$lang->execution->fixFirst = '修改首天工时';
+$lang->execution->team = '团队成员';
+$lang->execution->doc = '文档列表';
+$lang->execution->doclib = '文档库列表';
+$lang->execution->manageProducts = '关联' . $lang->productCommon;
+$lang->execution->linkStory = "关联{$lang->SRCommon}";
+$lang->execution->linkStoryByPlan = "按照计划关联";
+$lang->execution->linkPlan = "关联计划";
+$lang->execution->unlinkStoryTasks = "未关联{$lang->SRCommon}任务";
+$lang->execution->linkedProducts = '已关联';
+$lang->execution->unlinkedProducts = '未关联';
+$lang->execution->view = "{$lang->execution->common}概况";
+$lang->execution->startAction = "开始{$lang->execution->common}";
+$lang->execution->activateAction = "激活{$lang->execution->common}";
+$lang->execution->delayAction = "延期{$lang->execution->common}";
+$lang->execution->suspendAction = "挂起{$lang->execution->common}";
+$lang->execution->closeAction = "关闭{$lang->execution->common}";
+$lang->execution->testtaskAction = "{$lang->execution->common}测试单";
+$lang->execution->teamAction = "{$lang->execution->common}团队";
+$lang->execution->kanbanAction = "{$lang->execution->common}看板";
+$lang->execution->printKanbanAction = "打印看板";
+$lang->execution->treeAction = "{$lang->execution->common}树状图";
+$lang->execution->exportAction = "导出{$lang->execution->common}";
+$lang->execution->computeBurnAction = "计算燃尽图";
+$lang->execution->create = "添加{$lang->executionCommon}";
+$lang->execution->createExec = "添加{$lang->execution->common}";
+$lang->execution->createAction = "添加{$lang->execution->common}";
+$lang->execution->copyExec = "复制{$lang->execution->common}";
+$lang->execution->copy = "复制{$lang->executionCommon}";
+$lang->execution->delete = "删除{$lang->executionCommon}";
+$lang->execution->deleteAB = "删除{$lang->execution->common}";
+$lang->execution->browse = "浏览{$lang->execution->common}";
+$lang->execution->edit = "设置{$lang->executionCommon}";
+$lang->execution->editAction = "编辑{$lang->execution->common}";
+$lang->execution->batchEdit = "编辑";
+$lang->execution->batchEditAction = "批量编辑";
+$lang->execution->manageMembers = '团队管理';
+$lang->execution->unlinkMember = '移除成员';
+$lang->execution->unlinkStory = "移除{$lang->SRCommon}";
+$lang->execution->unlinkStoryAB = "移除{$lang->SRCommon}";
+$lang->execution->batchUnlinkStory = "批量移除{$lang->SRCommon}";
+$lang->execution->importTask = '转入任务';
+$lang->execution->importPlanStories = "按计划关联{$lang->SRCommon}";
+$lang->execution->importBug = '导入Bug';
+$lang->execution->tree = '树状图';
+$lang->execution->treeTask = '只看任务';
+$lang->execution->treeStory = "只看{$lang->SRCommon}";
+$lang->execution->treeViewTask = '树状图查看任务';
+$lang->execution->treeViewStory = "树状图查看{$lang->SRCommon}";
+$lang->execution->storyKanban = "{$lang->SRCommon}看板";
+$lang->execution->storySort = "{$lang->SRCommon}排序";
+$lang->execution->importPlanStory = '创建' . $lang->executionCommon . '成功!\n是否导入计划关联的相关' . $lang->SRCommon . '?导入时将自动过滤掉草稿状态的' . $lang->SRCommon . '。';
+$lang->execution->importEditPlanStory = '编辑' . $lang->executionCommon . '成功!\n是否导入计划关联的相关' . $lang->SRCommon . '?导入时将自动过滤掉草稿状态的' . $lang->SRCommon . '。';
+$lang->execution->importBranchPlanStory = '创建' . $lang->executionCommon . '成功!\n是否导入计划关联的相关' . $lang->SRCommon . '?导入时将只关联本' . $lang->executionCommon . '所关联分支的激活需求。';
+$lang->execution->importBranchEditPlanStory = '编辑' . $lang->executionCommon . '成功!\n是否导入计划关联的相关' . $lang->SRCommon . '?导入时将只关联本' . $lang->executionCommon . '所关联分支的激活需求。';
+$lang->execution->needLinkProducts = '该执行还未关联任何产品,相关功能无法使用,请先关联产品后再试。';
+$lang->execution->iteration = '版本迭代';
+$lang->execution->iterationInfo = '迭代%s次';
+$lang->execution->viewAll = '查看所有';
+$lang->execution->testreport = '测试报告';
+$lang->execution->taskKanban = '任务看板';
+$lang->execution->RDKanban = '研发看板';
/* 分组浏览。*/
$lang->execution->allTasks = '所有';
@@ -349,7 +351,8 @@ $lang->execution->howToUpdateBurn = "关联{$lang->SRCommon} 。";
-$lang->execution->haveDraft = "有%s条草稿状态的{$lang->SRCommon}无法关联到该{$lang->executionCommon}";
+$lang->execution->haveBranchDraft = "导入完成!有%s条非激活状态或不是{$lang->executionCommon}关联分支的{$lang->SRCommon}无法导入";
+$lang->execution->haveDraft = "导入完成!有%s条非激活状态的{$lang->SRCommon}无法导入";
$lang->execution->doneExecutions = '已结束';
$lang->execution->selectDept = '选择部门';
$lang->execution->selectDeptTitle = '选择一个部门的成员';
@@ -594,3 +597,6 @@ $lang->execution->gantt->exporting = '正在导出……';
$lang->execution->gantt->exportFail = '导出失败。';
$lang->execution->boardColorList = array('#32C5FF', '#006AF1', '#9D28B2', '#FF8F26', '#7FBB00', '#424BAC', '#66c5f8', '#EC2761');
+
+$lang->execution->linkBranchStoryByPlanTips = "执行按计划关联需求时,只导入本执行所关联%s的激活状态的需求。";
+$lang->execution->linkNormalStoryByPlanTips = "执行按计划关联需求时,只导入激活状态的需求。";
\ No newline at end of file
diff --git a/module/execution/model.php b/module/execution/model.php
index 1e83956238..f6be50c7f2 100755
--- a/module/execution/model.php
+++ b/module/execution/model.php
@@ -332,6 +332,20 @@ class executionModel extends model
$this->checkBeginAndEndDate($_POST['project'], $_POST['begin'], $_POST['end']);
if(dao::isError()) return false;
+ if($_POST['products'])
+ {
+ $this->app->loadLang('project');
+ $multipleProducts = $this->loadModel('product')->getMultiBranchPairs();
+ foreach($_POST['products'] as $index => $productID)
+ {
+ if(isset($multipleProducts[$productID]) and empty($_POST['branch'][$index]))
+ {
+ dao::$errors[] = $this->lang->project->emptyBranch;
+ return false;
+ }
+ }
+ }
+
/* Determine whether to add a sprint or a stage according to the model of the execution. */
$project = $this->loadModel('project')->getByID($_POST['project']);
$type = 'sprint';
@@ -514,6 +528,20 @@ class executionModel extends model
return false;
}
+ if($_POST['products'])
+ {
+ $this->app->loadLang('project');
+ $multipleProducts = $this->loadModel('product')->getMultiBranchPairs();
+ foreach($_POST['products'] as $index => $productID)
+ {
+ if(isset($multipleProducts[$productID]) and empty($_POST['branch'][$index]))
+ {
+ dao::$errors[] = $this->lang->project->emptyBranch;
+ return false;
+ }
+ }
+ }
+
/* Get the data from the post. */
$execution = fixer::input('post')
->add('id', $executionID)
@@ -2288,7 +2316,7 @@ class executionModel extends model
$planPairs = array('' => '');
foreach($products as $productID => $product)
{
- $plans = $this->productplan->getBranchPlanPairs($productID, array(BRANCH_MAIN) + $product->branches, true);
+ $plans = $this->productplan->getBranchPlanPairs($productID, array(BRANCH_MAIN) + $product->branches, 'unexpired', true);
foreach($plans as $plan) $planPairs += $plan;
}
$this->config->product->search['params']['plan']['values'] = $planPairs;
@@ -2370,23 +2398,28 @@ class executionModel extends model
$oldPlan = 0;
$branch = isset($branches[$i]) ? $branches[$i] : 0;
- if(isset($existedProducts[$productID][$branch])) continue;
+ if(!is_array($branch)) $branch = array($branch);
- if(isset($oldProducts[$productID][$branch]))
+ foreach($branch as $branchID)
{
- $oldProduct = $oldProducts[$productID][$branch];
- if($this->app->rawMethod != 'edit') $oldPlan = $oldProduct->plan;
- }
+ if(isset($existedProducts[$productID][$branchID])) continue;
- $data = new stdclass();
- $data->project = $executionID;
- $data->product = $productID;
- $data->branch = $branch;
- $data->plan = isset($plans[$productID][$branch]) ? implode(',', $plans[$productID][$branch]) : $oldPlan;
- $data->plan = trim($data->plan, ',');
- $data->plan = empty($data->plan) ? 0 : ",$data->plan,";
- $this->dao->insert(TABLE_PROJECTPRODUCT)->data($data)->exec();
- $existedProducts[$productID][$branch] = true;
+ if(isset($oldProducts[$productID][$branchID]))
+ {
+ $oldProduct = $oldProducts[$productID][$branchID];
+ if($this->app->rawMethod != 'edit') $oldPlan = $oldProduct->plan;
+ }
+
+ $data = new stdclass();
+ $data->project = $executionID;
+ $data->product = $productID;
+ $data->branch = $branchID;
+ $data->plan = isset($plans[$productID]) ? implode(',', $plans[$productID]) : $oldPlan;
+ $data->plan = trim($data->plan, ',');
+ $data->plan = empty($data->plan) ? 0 : ",$data->plan,";
+ $this->dao->insert(TABLE_PROJECTPRODUCT)->data($data)->exec();
+ $existedProducts[$productID][$branchID] = true;
+ }
}
$oldProductKeys = array_keys($oldProducts);
@@ -2882,19 +2915,21 @@ class executionModel extends model
*/
public function linkStories($executionID)
{
- $plans = $this->dao->select('plan')->from(TABLE_PROJECTPRODUCT)
+ $plans = $this->dao->select('product, plan')->from(TABLE_PROJECTPRODUCT)
->where('project')->eq($executionID)
- ->fetchPairs('plan');
+ ->fetchPairs('product', 'plan');
$planStories = array();
$planProducts = array();
$this->loadModel('story');
if(!empty($plans))
{
- foreach($plans as $planIdList)
+ $executionProducts = $this->loadModel('project')->getBranchesByProject($executionID);
+ foreach($plans as $productID => $planIdList)
{
if(empty($planIdList)) continue;
$planIdList = explode(',', $planIdList);
+ $executionBranches = zget($executionProducts, $productID, array());
foreach($planIdList as $planID)
{
$planStory = $this->story->getPlanStories($planID);
@@ -2902,7 +2937,7 @@ class executionModel extends model
{
foreach($planStory as $id => $story)
{
- if($story->status == 'draft' or $story->status == 'reviewing')
+ if($story->status != 'active' or (!empty($story->branch) and !empty($executionBranches) and !isset($executionBranches[$story->branch])))
{
unset($planStory[$id]);
continue;
@@ -4618,22 +4653,45 @@ class executionModel extends model
{
$this->loadModel('productplan');
+ $date = date('Y-m-d');
+
$param = strtolower($param);
$branchIdList = strpos($param, 'withmainplan') !== false ? array(BRANCH_MAIN => BRANCH_MAIN) : array();
$branchGroups = $this->getBranchByProduct(array_keys($products), $executionID, 'noclosed');
foreach($branchGroups as $branches)
{
- foreach($branches as $branchID => $branchName) $branchIdList[$branchID] = $branchID;
+ foreach($branches as $branchID => $branchName) $branchIdList[] = $branchID;
}
- $plans = $this->dao->select('id,title,product,parent,begin,end')->from(TABLE_PRODUCTPLAN)
- ->where('product')->in(array_keys($products))
- ->andWhere('deleted')->eq(0)
- ->andWhere('branch')->in($branchIdList)->fi()
- ->orderBy('begin desc')
+ $branchQuery = '(';
+ if(!empty($branchIdList))
+ {
+ $branchCount = count($branchIdList);
+ foreach($branchIdList as $index => $branchID)
+ {
+ $branchQuery .= "FIND_IN_SET('$branchID', branch)";
+ if($index < $branchCount - 1) $branchQuery .= ' OR ';
+ }
+ }
+ else
+ {
+ $branchQuery .= "FIND_IN_SET('0', branch)";
+ }
+
+ $branchQuery .= ')';
+
+ $plans = $this->dao->select('t1.id,t1.title,t1.product,t1.parent,t1.begin,t1.end,t1.branch,t2.type as productType')->from(TABLE_PRODUCTPLAN)->alias('t1')
+ ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t2.id=t1.product')
+ ->where('t1.product')->in(array_keys($products))
+ ->andWhere('t1.deleted')->eq(0)
+ ->andWhere($branchQuery)
+ ->beginIF(strpos($param, 'unexpired') !== false)->andWhere('t1.end')->ge($date)->fi()
+ ->beginIF(strpos($param, 'noclosed') !== false)->andWhere('t1.status')->ne('closed')->fi()
+ ->orderBy('t1.begin desc')
->fetchAll('id');
$plans = $this->productplan->reorder4Children($plans);
+ $plans = $this->productplan->relationBranch($plans);
$productPlans = array();
foreach($plans as $plan)
{
@@ -4641,6 +4699,7 @@ class executionModel extends model
if($plan->parent > 0 and isset($plans[$plan->parent])) $plan->title = $plans[$plan->parent]->title . ' /' . $plan->title;
$productPlans[$plan->product][$plan->id] = $plan->title . " [{$plan->begin} ~ {$plan->end}]";
if($plan->begin == '2030-01-01' and $plan->end == '2030-01-01') $productPlans[$plan->product][$plan->id] = $plan->title . ' ' . $this->lang->productplan->future;
+ if($plan->productType != 'normal') $productPlans[$plan->product][$plan->id] = $productPlans[$plan->product][$plan->id] . ' / ' . ($plan->branchName ? $plan->branchName : $this->lang->branch->main);
}
return $productPlans;
@@ -5202,7 +5261,7 @@ class executionModel extends model
$_POST['status'] = $project->status;
$_POST['acl'] = 'open';
- if(!empty($_POST['code'])) $_POST['code'] = $project->code;
+ if(!isset($this->config->setCode) or $this->config->setCode == 1) $_POST['code'] = $project->code;
$projectProducts = $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->fetchAll();
foreach($projectProducts as $projectProduct)
diff --git a/module/execution/view/build.html.php b/module/execution/view/build.html.php
index c1610c1616..38b6f48b2e 100644
--- a/module/execution/view/build.html.php
+++ b/module/execution/view/build.html.php
@@ -49,10 +49,12 @@
build->id;?>
- build->product;?>
+ build->product;?>
+
+ build->branch;?>
+
build->name;?>
- build->scmPath;?>
- build->filePath;?>
+ build->url;?>
build->date;?>
build->builder;?>
actions;?>
@@ -64,12 +66,30 @@
id"), sprintf('%03d', $build->id));?>
productName;?>
-
- branchName) echo "{$build->branchName} "?>
- createLink('build', 'view', "build=$build->id"), $build->name);?>
+
+ branchName;?>
+
+ createLink('build', 'view', "build=$build->id"), $build->name);?>
+
+ scmPath)
+ {
+ $colorStyle = strpos($build->scmPath, 'http') === 0 ? "style='color:#2463c7;'" : '';
+ echo " ";
+ echo "";
+ echo $colorStyle ? html::a($build->scmPath, $build->scmPath, '_blank', $colorStyle) : $build->scmPath;
+ echo '
';
+ }
+ if($build->filePath)
+ {
+ $colorStyle = strpos($build->filePath, 'http') === 0 ? "style='color:#2463c7;'" : '';
+ echo " ";
+ echo "";
+ echo $colorStyle ? html::a($build->filePath, $build->filePath, '_blank', $colorStyle) : $build->filePath;
+ echo '
';
+ }
+ ?>
- scmPath, 'http') === 0 ? html::a($build->scmPath) : $build->scmPath;?>
- filePath, 'http') === 0 ? html::a($build->filePath) : $build->filePath;?>
date?>
builder);?>
build->buildOperateMenu($build, 'browse', "executionID={$execution->id}&productID={$productID}");?>
diff --git a/module/execution/view/create.html.php b/module/execution/view/create.html.php
index 902cd65b1d..87b0986e9f 100644
--- a/module/execution/view/create.html.php
+++ b/module/execution/view/create.html.php
@@ -130,62 +130,81 @@
printExtendFields('', 'table', 'columns=3');?>
hasProduct)) $hidden = ''?>
+
+
+
- execution->manageProducts;?>
-
+ project->manageProductPlan;?>
+
-
-
-
- type != 'normal' and isset($branchGroups[$product->id]));?>
- id] as $branchID => $branch):?>
-
-
- model) and !empty($project->hasProduct) and $project->model == 'scrum') $hidden = '';?>
-
- execution->linkPlan;?>
-
+
+
+
+
+ project->manageProductPlan;?>
+
- begin)):?>
-
product}][{$plan->branch}][]", $productPlan, $plan->id, "class='form-control chosen' multiple");?>
- id)?>
-
-
-
- id] as $branchID => $branch):?>
- id][$branchID]) ? $productPlans[$product->id][$branchID] : array();?>
-
id}][$branchID][]", $plans, $branches[$product->id][$branchID]->plan, "class='form-control chosen' multiple");?>
-
-
-
-
-
-
-
+
+
+
+
+ product->common;?>
+
+
+
+
+
+ project->branch;?>
+
+
+
+
+
+
+
execution->teamname;?>
diff --git a/module/execution/view/edit.html.php b/module/execution/view/edit.html.php
index 0f6507834d..f2eaf83b23 100644
--- a/module/execution/view/edit.html.php
+++ b/module/execution/view/edit.html.php
@@ -141,56 +141,81 @@
attribute, array('request', 'design', 'review'))): ?>
hasProduct)) $hidden = ''?>
+
+
+
- execution->manageProducts;?>
-
- grade == 2 or $execution->type == 'stage') ? "disabled" : '';?>
+ project->manageProductPlan;?>
+
-
-
- type != 'normal' and isset($branchGroups[$product->id]);?>
- id] as $branchID => $branch):?>
-
- model) and $project->model == 'scrum') $hidden = '';?>
-
- execution->linkPlan;?>
-
+
+
+
+
+ project->manageProductPlan;?>
+
-
-
-
-
-
- id] as $branchID => $branch):?>
- id][$branchID]) ? $productPlans[$product->id][$branchID] : array();?>
-
id}][{$branchID}][]", $plans, $branches[$product->id][$branchID]->plan, "class='form-control chosen' multiple");?>
-
-
-
-
+
+
+
+
+ product->common;?>
+
+
+
+
+
+ product->branchName['branch'];?>
+
+
+
+
+
+
+
@@ -232,5 +257,6 @@
execution->confirmSync);?>
+project);?>
project->unLinkProductTip);?>
diff --git a/module/execution/view/story.html.php b/module/execution/view/story.html.php
index 15d06fd689..bb35137bae 100644
--- a/module/execution/view/story.html.php
+++ b/module/execution/view/story.html.php
@@ -402,13 +402,17 @@
- execution->linkStory, "id='toTaskButton'", 'btn btn-primary');?>
+
+ execution->linkStory, "id='toTaskButton'", 'btn btn-primary ' . $disabled);?>
diff --git a/module/file/control.php b/module/file/control.php
index c19efc9cc2..7bf6ae7588 100755
--- a/module/file/control.php
+++ b/module/file/control.php
@@ -131,7 +131,7 @@ class file extends control
{
echo(js::alert($this->lang->file->accessDenied));
if(isonlybody()) return print(js::reload('parent.parent'));
- return print(js::locate(helper::createLink('my', 'index')));
+ return print(js::locate(helper::createLink('my', 'index'), 'parent.parent'));
}
/* Judge the mode, down or open. */
diff --git a/module/group/lang/resource.php b/module/group/lang/resource.php
index 7d201a2773..ed99894093 100644
--- a/module/group/lang/resource.php
+++ b/module/group/lang/resource.php
@@ -547,6 +547,7 @@ $lang->resource->story->batchChangeModule = 'batchChangeModule';
$lang->resource->story->batchToTask = 'batchToTask';
$lang->resource->story->processStoryChange = 'processStoryChange';
$lang->resource->story->linkStories = 'linkStoriesAB';
+$lang->resource->story->relieved = 'relievedTwins';
$lang->story->methodOrder[5] = 'create';
$lang->story->methodOrder[10] = 'batchCreate';
@@ -576,6 +577,7 @@ $lang->story->methodOrder[120] = 'batchChangeModule';
$lang->story->methodOrder[125] = 'batchToTask';
$lang->story->methodOrder[130] = 'processStoryChange';
$lang->story->methodOrder[135] = 'linkStories';
+$lang->story->methodOrder[140] = 'relieved';
/* Requirement. */
$lang->resource->requirement = new stdclass();
diff --git a/module/holiday/model.php b/module/holiday/model.php
index cfffe7ff23..2b4a05d0f3 100644
--- a/module/holiday/model.php
+++ b/module/holiday/model.php
@@ -250,8 +250,8 @@ class holidayModel extends model
/**
* Get diff days.
*
- * @param varchar $begin
- * @param varchar $end
+ * @param string $begin
+ * @param string $end
* @access public
* @return bool
*/
diff --git a/module/index/css/index.css b/module/index/css/index.css
index a82f6b512f..c01012df2e 100644
--- a/module/index/css/index.css
+++ b/module/index/css/index.css
@@ -49,7 +49,8 @@ body.menu-hide {padding-left: 0;}
.menu-hide #apps {left: 40px;}
.app-container.loading:before {background-color: rgba(0,0,0,.1);}
.app-container.loading:before, .app-container.loading::after {transition-delay: 3s;}
-.app-container.loading.open-from-hidden {transition: 0s;}
+.app-container.loading.open-from-hidden {transition-delay: 0s;}
+.app-container.loading.open-from-hidden > iframe {opacity: 0;}
#appsBar {position: fixed; left: 96px; bottom: 0; right: 0; height: 40px; z-index: 1012; background: #fff; border-top: 1px solid #eff1f7;}
.menu-hide #appsBar {left: 40px;}
diff --git a/module/index/js/index.js b/module/index/js/index.js
index aba2977080..0a8af0d13d 100644
--- a/module/index/js/index.js
+++ b/module/index/js/index.js
@@ -268,10 +268,10 @@
var isSameUrl = iframe && url && iframe.contentWindow.location.href.endsWith(url);
if (url && (!isSameUrl || forceReload !== false))
{
- app.$app.toggleClass('open-from-hidden', app.$app.is(':hidden'))
+ app.$app.toggleClass('open-from-hidden', app.zIndex < openedAppZIndex)
reloadApp(appCode, url, true);
}
- app.zIndex = openedAppZIndex++;
+ app.zIndex = ++openedAppZIndex;
app.$app.show().css('z-index', app.zIndex);
/* Update task bar */
@@ -464,7 +464,7 @@
if(!notTriggerEvent) app.$app.trigger('reloadapp', app);
- if(!isSameUrl) app.$app.addClass('loading');
+ if(!isSameUrl || app.zIndex < openedAppZIndex) app.$app.addClass('loading');
if(app._loadTimer) clearTimeout(app._loadTimer);
app._loadTimer = setTimeout(function()
{
diff --git a/module/kanban/model.php b/module/kanban/model.php
index 046a94de6e..9c1b8d325e 100755
--- a/module/kanban/model.php
+++ b/module/kanban/model.php
@@ -818,8 +818,11 @@ class kanbanModel extends model
}
elseif($branchID)
{
- $branchName = $this->branch->getById($branchID);
- $branches = array($branchID => $branchName);
+ foreach(explode(',', $branchID) as $id)
+ {
+ $branchName = $this->branch->getById($id);
+ $branches[$id] = $branchName;
+ }
}
foreach($branches as $id => $name)
diff --git a/module/misc/lang/de.php b/module/misc/lang/de.php
index 15242cc32a..9d4b533cdb 100644
--- a/module/misc/lang/de.php
+++ b/module/misc/lang/de.php
@@ -103,6 +103,7 @@ $lang->misc->feature->themeDesc = 'ZenTao 15.0+ a new "Youth Blue" theme
$lang->misc->feature->visionsDesc = "
The concept of interface has been added since 16.5. Users can deal with R&D affairs in [Full Feature Interface] and daily office affairs in [Operation Management Interface] .
You can view the current interface on the avatar, and click the name of the interface to view and switch other interfaces.
";
$lang->misc->feature->visionsImage = 'theme/default/images/main/visions_en.png';
+$lang->misc->releaseDate['18.0.beta2'] = '2022-12-14';
$lang->misc->releaseDate['18.0.beta1'] = '2022-11-16';
$lang->misc->releaseDate['17.8'] = '2022-11-02';
$lang->misc->releaseDate['17.7'] = '2022-10-19';
@@ -193,6 +194,7 @@ $lang->misc->releaseDate['7.2.stable'] = '2015-05-22';
$lang->misc->releaseDate['7.1.stable'] = '2015-03-07';
$lang->misc->releaseDate['6.3.stable'] = '2014-11-07';
+$lang->misc->feature->all['18.0.beta2'][] = array('title' => "We have optimized the product with multi-branches/multi-platforms. It's supported to create siblings stories. It's possible for Plan, Build, and Release to link requirements or bugs beyond their parent branch. 5 BI big screens are newly added. ZenTao Client has implemented the robot chatting mechanism.", 'desc' => '');
$lang->misc->feature->all['18.0.beta1'][] = array('title' => "Multiple core processes in ZenTao are improved: A project without any product linked in is able to be created, as well as creating projects without sprints/iterations linked in; projects are able to link products beyond the father program; switch easily between ZenTao Lite mode and Full Lifecycle Management mode.", 'desc' => '');
$lang->misc->feature->all['17.8'][] = array('title' => "We have optimized the color of status in lists, as well as the dashboard color. At the same time, the page of the task effort has been improved.", 'desc' => '');
$lang->misc->feature->all['17.7'][] = array('title' => "The table is optimized in the transition version. At the same time, we have added the new feature of Work Order and get the Feedback features improved as well. Fix bugs.", 'desc' => '');
diff --git a/module/misc/lang/en.php b/module/misc/lang/en.php
index 9ace3c33ea..e8fddaf594 100644
--- a/module/misc/lang/en.php
+++ b/module/misc/lang/en.php
@@ -103,6 +103,7 @@ $lang->misc->feature->themeDesc = 'ZenTao 15.0+ a new "Youth Blue" theme
$lang->misc->feature->visionsDesc = "
The concept of interface has been added since 16.5. Users can deal with R&D affairs in [Full Feature Interface] and daily office affairs in [Operation Management Interface] .
You can view the current interface on the avatar, and click the name of the interface to view and switch other interfaces.
";
$lang->misc->feature->visionsImage = 'theme/default/images/main/visions_en.png';
+$lang->misc->releaseDate['18.0.beta2'] = '2022-12-14';
$lang->misc->releaseDate['18.0.beta1'] = '2022-11-16';
$lang->misc->releaseDate['17.8'] = '2022-11-02';
$lang->misc->releaseDate['17.7'] = '2022-10-19';
@@ -193,6 +194,7 @@ $lang->misc->releaseDate['7.2.stable'] = '2015-05-22';
$lang->misc->releaseDate['7.1.stable'] = '2015-03-07';
$lang->misc->releaseDate['6.3.stable'] = '2014-11-07';
+$lang->misc->feature->all['18.0.beta2'][] = array('title' => "We have optimized the product with multi-branches/multi-platforms. It's supported to create siblings stories. It's possible for Plan, Build, and Release to link requirements or bugs beyond their parent branch. 5 BI big screens are newly added. ZenTao Client has implemented the robot chatting mechanism.", 'desc' => '');
$lang->misc->feature->all['18.0.beta1'][] = array('title' => "Multiple core processes in ZenTao are improved: A project without any product linked in is able to be created, as well as creating projects without sprints/iterations linked in; projects are able to link products beyond the father program; switch easily between ZenTao Lite mode and Full Lifecycle Management mode.", 'desc' => '');
$lang->misc->feature->all['17.8'][] = array('title' => "We have optimized the color of status in lists, as well as the dashboard color. At the same time, the page of the task effort has been improved.", 'desc' => '');
$lang->misc->feature->all['17.7'][] = array('title' => "The table is optimized in the transition version. At the same time, we have added the new feature of Work Order and get the Feedback features improved as well. Fix bugs.", 'desc' => '');
diff --git a/module/misc/lang/fr.php b/module/misc/lang/fr.php
index 2792a3ce78..35a3d8c558 100644
--- a/module/misc/lang/fr.php
+++ b/module/misc/lang/fr.php
@@ -103,6 +103,7 @@ $lang->misc->feature->themeDesc = 'ZenTao 15.0+ a new "Youth Blue" theme
$lang->misc->feature->visionsDesc = "
The concept of interface has been added since 16.5. Users can deal with R&D affairs in [Full Feature Interface] and daily office affairs in [Operation Management Interface] .
You can view the current interface on the avatar, and click the name of the interface to view and switch other interfaces.
";
$lang->misc->feature->visionsImage = 'theme/default/images/main/visions_en.png';
+$lang->misc->releaseDate['18.0.beta2'] = '2022-12-14';
$lang->misc->releaseDate['18.0.beta1'] = '2022-11-16';
$lang->misc->releaseDate['17.8'] = '2022-11-02';
$lang->misc->releaseDate['17.7'] = '2022-10-19';
@@ -193,6 +194,7 @@ $lang->misc->releaseDate['7.2.stable'] = '2015-05-22';
$lang->misc->releaseDate['7.1.stable'] = '2015-03-07';
$lang->misc->releaseDate['6.3.stable'] = '2014-11-07';
+$lang->misc->feature->all['18.0.beta2'][] = array('title' => "We have optimized the product with multi-branches/multi-platforms. It's supported to create siblings stories. It's possible for Plan, Build, and Release to link requirements or bugs beyond their parent branch. 5 BI big screens are newly added. ZenTao Client has implemented the robot chatting mechanism.", 'desc' => '');
$lang->misc->feature->all['18.0.beta1'][] = array('title' => "Multiple core processes in ZenTao are improved: A project without any product linked in is able to be created, as well as creating projects without sprints/iterations linked in; projects are able to link products beyond the father program; switch easily between ZenTao Lite mode and Full Lifecycle Management mode.", 'desc' => '');
$lang->misc->feature->all['17.8'][] = array('title' => "We have optimized the color of status in lists, as well as the dashboard color. At the same time, the page of the task effort has been improved.", 'desc' => '');
$lang->misc->feature->all['17.7'][] = array('title' => "The table is optimized in the transition version. At the same time, we have added the new feature of Work Order and get the Feedback features improved as well. Fix bugs.", 'desc' => '');
diff --git a/module/misc/lang/zh-cn.php b/module/misc/lang/zh-cn.php
index c235c42d89..e0081a5b62 100644
--- a/module/misc/lang/zh-cn.php
+++ b/module/misc/lang/zh-cn.php
@@ -103,6 +103,7 @@ $lang->misc->feature->themeDesc = "禅道15系列上线了全新的“
$lang->misc->feature->visionsDesc = "
从16.5开始增加了界面概念,用户可以在[研发综合界面] 中处理研发事务、在[运营管理界面] 处理日常办公事务。
在头像右侧即可查看当前所处界面,点击当前界面名称可查看和切换其他的界面。
";
$lang->misc->feature->visionsImage = 'theme/default/images/main/visions.png';
+$lang->misc->releaseDate['18.0.beta2'] = '2022-12-14';
$lang->misc->releaseDate['18.0.beta1'] = '2022-11-16';
$lang->misc->releaseDate['17.8'] = '2022-11-02';
$lang->misc->releaseDate['17.7'] = '2022-10-19';
@@ -193,6 +194,7 @@ $lang->misc->releaseDate['7.2.stable'] = '2015-05-22';
$lang->misc->releaseDate['7.1.stable'] = '2015-03-07';
$lang->misc->releaseDate['6.3.stable'] = '2014-11-07';
+$lang->misc->feature->all['18.0.beta2'][] = array('title' => '优化多分支/多平台产品,支持创建孪生需求,计划、版本、发布支持跨分支关联需求和bug,新增5个BI大屏,并且禅道客户端实现了机器人会话机制。', 'desc' => '');
$lang->misc->feature->all['18.0.beta1'][] = array('title' => '主要对禅道多项核心流程进行改进,新增项目型项目、无迭代项目;支持项目跨项目集关联产品;支持轻量管理模式和全生命周期管理模式进行切换。', 'desc' => '');
$lang->misc->feature->all['17.8'][] = array('title' => '列表状态颜色、仪表盘颜色的改版和任务日志页面的优化。', 'desc' => '');
$lang->misc->feature->all['17.7'][] = array('title' => '过渡版本表格优化完成。新增工单功能,优化了反馈功能。修复Bug。', 'desc' => '');
@@ -330,7 +332,7 @@ $lang->misc->feature->all['8.2.stable'][] = array('title'=>'导出自定义', 'd
$lang->misc->feature->all['8.2.stable'][] = array('title'=>"{$lang->SRCommon}、任务、Bug、用例组合检索功能", 'desc' => "在{$lang->SRCommon}、任务、Bug、用例列表页面,可以实现模块和标签的组合检索。
");
$lang->misc->feature->all['8.2.stable'][] = array('title'=>'增加新手教程', 'desc' => '增加新手教程,方便新用户了解禅道使用。
');
-$lang->misc->feature->all['7.4.beta'][] = array('title'=>'产品实现分支功能', 'desc' => "产品增加分支/平台类型,相应的{$lang->SRCommon}、计划、Bug、用例、模块等都增加分支。
");
+$lang->misc->feature->all['7.4.beta'][] = array('title'=>'产品实现分支功能', 'desc' => "产品增加平台/分支类型,相应的{$lang->SRCommon}、计划、Bug、用例、模块等都增加分支。
");
$lang->misc->feature->all['7.4.beta'][] = array('title'=>'调整发布模块', 'desc' => '发布增加停止维护操作,当发布停止维护时,创建Bug将不显示这个发布。
发布中遗留的bug改为手工关联。
');
$lang->misc->feature->all['7.4.beta'][] = array('title'=>"调整{$lang->SRCommon}和Bug的创建页面", 'desc' => '');
diff --git a/module/my/config.php b/module/my/config.php
index d4020cd11e..c7d74d5ed1 100644
--- a/module/my/config.php
+++ b/module/my/config.php
@@ -9,10 +9,7 @@ $config->my->taskCounts = 10;
$config->my->bugCounts = 10;
$config->my->storyCounts = 10;
-$config->my->oaObjectType = 'attend,leave,makeup,overtime,lieu';
-$config->my->reviewObjectType = 'story,case';
-if($config->edition == 'biz') $config->my->reviewObjectType = 'story,case,feedback,attend,leave,makeup,overtime,lieu';
-if($config->edition == 'max') $config->my->reviewObjectType = 'story,case,review,feedback,attend,leave,makeup,overtime,lieu';
+$config->my->oaObjectType = 'attend,leave,makeup,overtime,lieu';
$config->mobile = new stdclass();
$config->mobile->todoBar = array('today', 'yesterday', 'thisWeek', 'lastWeek', 'all');
diff --git a/module/my/control.php b/module/my/control.php
index 9474df1719..75e03d3233 100755
--- a/module/my/control.php
+++ b/module/my/control.php
@@ -955,15 +955,27 @@ EOF;
$this->app->loadClass('pager', true);
$pager = pager::init($recTotal, $recPerPage, $pageID);
+ $typeList = array();
if($this->app->rawMethod == 'contribute')
{
$reviewList = $this->my->getReviewedList($browseType, $orderBy, $pager);
}
else
{
+ $typeList = $this->my->getReviewingTypeList();
+ if(!isset($typeList->$browseType)) $browseType = 'all';
+
+ $this->lang->my->auditMenu->audit = $typeList;
$reviewList = $this->my->getReviewingList($browseType, $orderBy, $pager);
}
+ $this->view->flows = array();
+ if($this->config->edition == 'max')
+ {
+ $this->app->loadLang('approval');
+ $this->view->flows = $this->dao->select('module,name')->from(TABLE_WORKFLOW)->where('buildin')->eq(0)->fetchPairs('module', 'name');
+ }
+
$this->view->title = $this->lang->review->common;
$this->view->users = $this->loadModel('user')->getPairs('noclosed|noletter');
$this->view->reviewList = $reviewList;
diff --git a/module/my/model.php b/module/my/model.php
index 7cb33794fd..38b1fd9612 100644
--- a/module/my/model.php
+++ b/module/my/model.php
@@ -993,6 +993,34 @@ class myModel extends model
return $requirements;
}
+ /**
+ * Get reviewing type list for menu.
+ *
+ * @access public
+ * @return object
+ */
+ public function getReviewingTypeList()
+ {
+ $typeList = array();
+ if($this->getReviewingStories('id_desc', true)) $typeList[] = 'story';
+ if($this->getReviewingCases('id_desc', true)) $typeList[] = 'testcase';
+ if($this->getReviewingApprovals('id_desc', true)) $typeList[] = 'project';
+ if($this->getReviewingFeedbacks('id_desc', true)) $typeList[] = 'feedback';
+ if($this->getReviewingOA('status', true)) $typeList[] = 'oa';
+ $typeList = array_merge($typeList, $this->getReviewingFlows('all', 'id_desc', true));
+
+ $flows = ($this->config->edition == 'open') ? array() : $this->dao->select('module,name')->from(TABLE_WORKFLOW)->where('module')->in($typeList)->andWhere('buildin')->eq(0)->fetchPairs('module', 'name');
+ $menu = new stdclass();
+ $menu->all = $this->lang->my->auditMenu->audit->all;
+ foreach($typeList as $type)
+ {
+ $this->app->loadLang($type);
+ $menu->$type = isset($this->lang->$type->common) ? $this->lang->$type->common : zget($flows, $type);
+ }
+
+ return $menu;
+ }
+
/**
* Get reviewing list for me.
*
@@ -1010,6 +1038,7 @@ class myModel extends model
if($browseType == 'all' or $browseType == 'project') $reviewList = array_merge($reviewList, $this->getReviewingApprovals());
if($browseType == 'all' or $browseType == 'feedback') $reviewList = array_merge($reviewList, $this->getReviewingFeedbacks());
if($browseType == 'all' or $browseType == 'oa') $reviewList = array_merge($reviewList, $this->getReviewingOA());
+ if($browseType == 'all' or !in_array($browseType, array('story', 'testcase', 'feedback', 'oa'))) $reviewList = array_merge($reviewList, $this->getReviewingFlows($browseType));
if(empty($reviewList)) return array();
@@ -1044,10 +1073,11 @@ class myModel extends model
* Get reviewing stories.
*
* @param string $orderBy
+ * @param bool $checkExists
* @access public
* @return array
*/
- public function getReviewingStories($orderBy = 'id_desc')
+ public function getReviewingStories($orderBy = 'id_desc', $checkExists = false)
{
if(!common::hasPriv('story', 'review')) return array();
@@ -1066,6 +1096,7 @@ class myModel extends model
$stories = array();
while($data = $stmt->fetch())
{
+ if($checkExists) return true;
$story = new stdclass();
$story->id = $data->id;
$story->title = $data->title;
@@ -1084,10 +1115,11 @@ class myModel extends model
* Get reviewing cases.
*
* @param string $orderBy
+ * @param bool $checkExists
* @access public
* @return array
*/
- public function getReviewingCases($orderBy = 'id_desc')
+ public function getReviewingCases($orderBy = 'id_desc', $checkExists = false)
{
if(!common::hasPriv('testcase', 'review')) return array();
@@ -1102,6 +1134,7 @@ class myModel extends model
$cases = array();
while($data = $stmt->fetch())
{
+ if($checkExists) return true;
$case = new stdclass();
$case->id = $data->id;
$case->title = $data->title;
@@ -1118,10 +1151,11 @@ class myModel extends model
* Get reviewing approvals.
*
* @param string $orderBy
+ * @param bool $checkExists
* @access public
* @return array
*/
- public function getReviewingApprovals($orderBy = 'id_desc')
+ public function getReviewingApprovals($orderBy = 'id_desc', $checkExists = false)
{
if(!common::hasPriv('review', 'assess')) return array();
if($this->config->edition != 'max') return array();
@@ -1136,11 +1170,12 @@ class myModel extends model
foreach($projectReviews as $review)
{
if(!isset($pendingList[$review->id])) continue;
+ if($checkExists) return true;
$data = new stdclass();
$data->id = $review->id;
$data->title = $review->title;
- $data->type = 'project';
+ $data->type = 'projectreview';
$data->time = $review->createdDate;
$data->status = $review->status;
$reviewList[] = $data;
@@ -1149,13 +1184,80 @@ class myModel extends model
}
/**
- * Get reviewing feedbacks.
+ * Get reviewing for flows setting.
*
+ * @param string $objectType
* @param string $orderBy
+ * @param bool $checkExists
* @access public
* @return array
*/
- public function getReviewingFeedbacks($orderBy = 'id_desc')
+ public function getReviewingFlows($objectType = 'all', $orderBy = 'id_desc', $checkExists = false)
+ {
+ if($this->config->edition != 'max') return array();
+
+ $stmt = $this->dao->select('t2.objectType,t2.objectID')->from(TABLE_APPROVALNODE)->alias('t1')
+ ->leftJoin(TABLE_APPROVALOBJECT)->alias('t2')
+ ->on('t2.approval = t1.approval')
+ ->where('t2.objectType')->ne('review')
+ ->beginIF($objectType != 'all')->andWhere('t2.objectType')->eq($objectType)->fi()
+ ->andWhere('t1.account')->eq($this->app->user->account)
+ ->andWhere('t1.status')->eq('doing')
+ ->query();
+ $objectIdList = array();
+ while($object = $stmt->fetch()) $objectIdList[$object->objectType][$object->objectID] = $object->objectID;
+ if($checkExists) return array_keys($objectIdList);
+
+ $flows = $this->dao->select('module,`table`,name,titleField')->from(TABLE_WORKFLOW)->where('module')->in(array_keys($objectIdList))->andWhere('buildin')->eq(0)->fetchAll('module');
+ $objectGroup = array();
+ foreach($objectIdList as $objectType => $idList)
+ {
+ $table = zget($this->config->objectTables, $objectType, '');
+ if(empty($table) and isset($flows[$objectType])) $table = $flows[$objectType]->table;
+ if(empty($table)) continue;
+
+ $objectGroup[$objectType] = $this->dao->select('*')->from($table)->where('id')->in($idList)->fetchAll('id');
+ }
+
+ $this->app->loadConfig('action');
+ $approvalList = array();
+ foreach($objectGroup as $objectType => $objects)
+ {
+ $title = '';
+ $titleFieldName = zget($this->config->action->objectNameFields, $objectType, '');
+ $openedDateField = 'openedDate';
+ if(in_array($objectType, array('product', 'productplan', 'release', 'build', 'testtask'))) $openedDateField = 'createdDate';
+ if(in_array($objectType, array('testsuite', 'caselib')))$openedDateField = 'addedDate';
+ if(empty($titleFieldName) and isset($flows[$objectType]))
+ {
+ if(!empty($flows[$objectType]->titleField)) $titleFieldName = $flows[$objectType]->titleField;
+ if(empty($flows[$objectType]->titleField)) $title = $flows[$objectType]->name;
+ $openedDateField = 'createdDate';
+ }
+
+ foreach($objects as $object)
+ {
+ $data = new stdclass();
+ $data->id = $object->id;
+ $data->title = (empty($titleFieldName) or !isset($object->$titleFieldName)) ? $title . " #{$object->id}" : $object->$titleFieldName;
+ $data->type = $objectType;
+ $data->time = $object->$openedDateField;
+ $data->status = 'doing';
+ $approvalList[] = $data;
+ }
+ }
+ return $approvalList;
+ }
+
+ /**
+ * Get reviewing feedbacks.
+ *
+ * @param string $orderBy
+ * @param bool $checkExists
+ * @access public
+ * @return array
+ */
+ public function getReviewingFeedbacks($orderBy = 'id_desc', $checkExists = false)
{
if(!common::hasPriv('feedback', 'review')) return array();
if($this->config->edition == 'open') return array();
@@ -1164,6 +1266,8 @@ class myModel extends model
$reviewList = array();
foreach($feedbacks as $feedback)
{
+ if($checkExists) return true;
+
$data = new stdclass();
$data->id = $feedback->id;
$data->title = $feedback->title;
@@ -1179,10 +1283,11 @@ class myModel extends model
* Get reviewing OA.
*
* @param string $orderBy
+ * @param bool $checkExists
* @access public
* @return array
*/
- public function getReviewingOA($orderBy = 'status')
+ public function getReviewingOA($orderBy = 'status', $checkExists = false)
{
if($this->config->edition == 'open') return array();
@@ -1203,6 +1308,14 @@ class myModel extends model
if(common::hasPriv('makeup', 'review') and common::hasPriv('makeup', 'view')) $oa['makeup'] = $this->getReviewingMakeups($allDeptList, $managedDeptList, $orderBy);
if(common::hasPriv('lieu', 'review') and common::hasPriv('lieu', 'view')) $oa['lieu'] = $this->getReviewingLieus($allDeptList, $managedDeptList, $orderBy);
+ if($checkExists)
+ {
+ foreach($oa as $type => $reviewings)
+ {
+ if(!empty($reviewings)) return true;
+ }
+ }
+
$reviewList = array();
foreach($oa as $type => $reviewings)
{
@@ -1251,19 +1364,19 @@ class myModel extends model
if(empty($actionField)) $actionField = 'date';
$orderBy = $actionField . '_' . $direction;
- $condition = "action = 'reviewed'";
+ $condition = "(action = 'reviewed' or action = 'approvalreview')";
if($browseType == 'createdbyme')
{
$condition = "(objectType in('story','case','feedback') and action = 'submitreview') OR ";
$condition .= "(objectType = 'review' and action = 'opened') OR ";
$condition .= "(objectType = 'attend' and action = 'commited') OR ";
+ $condition .= "(action = 'approvalsubmit') OR ";
$condition .= "(objectType in('leave','makeup','overtime','lieu') and action = 'created')";
$condition = "($condition)";
}
$actions = $this->dao->select('objectType,objectID,actor,action,MAX(`date`) as `date`,extra')->from(TABLE_ACTION)
->where('actor')->eq($this->app->user->account)
- ->andWhere('objectType')->in($this->config->my->reviewObjectType)
->andWhere('vision')->eq($this->config->vision)
->andWhere($condition)
->groupBy('objectType,objectID')
@@ -1273,10 +1386,12 @@ class myModel extends model
$objectTypeList = array();
foreach($actions as $action) $objectTypeList[$action->objectType][] = $action->objectID;
+ $flows = ($this->config->edition == 'open') ? array() : $this->dao->select('module,`table`,name,titleField')->from(TABLE_WORKFLOW)->where('module')->in(array_keys($objectTypeList))->andWhere('buildin')->eq(0)->fetchAll('module');
$objectGroup = array();
foreach($objectTypeList as $objectType => $idList)
{
$table = zget($this->config->objectTables, $objectType, '');
+ if(empty($table) and isset($flows[$objectType])) $table = $flows[$objectType]->table;
if(empty($table)) continue;
$objectGroup[$objectType] = $this->dao->select('*')->from($table)->where('id')->in($idList)->fetchAll('id');
@@ -1290,6 +1405,7 @@ class myModel extends model
}
$users = $this->loadModel('user')->getPairs('noletter');
+ $this->app->loadConfig('action');
$reviewList = array();
foreach($actions as $action)
{
@@ -1302,10 +1418,10 @@ class myModel extends model
$review->type = $objectType;
$review->time = $action->date;
$review->result = strtolower($action->extra);
- $review->status = $objectType == 'attend' ? $object->reviewStatus : $object->status;
+ $review->status = $objectType == 'attend' ? $object->reviewStatus : ((isset($object->status) and !isset($flows[$objectType])) ? $object->status : 'done');
if(strpos($review->result, ',') !== false) list($review->result) = explode(',', $review->result);
- if($review->type == 'review') $review->type = 'project';
+ if($review->type == 'review') $review->type = 'projectreview';
if($review->type == 'case') $review->type = 'testcase';
$review->title = '';
if(isset($object->title))
@@ -1320,6 +1436,17 @@ class myModel extends model
{
$review->title = sprintf($this->lang->my->auditField->oaTitle[$objectType], zget($users, $object->createdBy), $object->begin . ' ' . substr($object->start, 0, 5) . ' ~ ' . $object->end . ' ' . substr($object->finish, 0, 5));
}
+ else
+ {
+ $title = '';
+ $titleFieldName = zget($this->config->action->objectNameFields, $objectType, '');
+ if(empty($titleFieldName) and isset($flows[$objectType]))
+ {
+ if(!empty($flows[$objectType]->titleField)) $titleFieldName = $flows[$objectType]->titleField;
+ if(empty($flows[$objectType]->titleField)) $title = $flows[$objectType]->name;
+ }
+ $review->title = (empty($titleFieldName) or !isset($object->$titleFieldName)) ? $title . " #{$object->id}" : $object->$titleFieldName;
+ }
$reviewList[] = $review;
}
diff --git a/module/my/view/audit.html.php b/module/my/view/audit.html.php
index 3a999df645..1e2320c92e 100644
--- a/module/my/view/audit.html.php
+++ b/module/my/view/audit.html.php
@@ -53,13 +53,22 @@
type;
- if($type == 'project') $type = 'review';
+ if($type == 'projectreview') $type = 'review';
- $typeName = $lang->{$review->type}->common;
+ $typeName = '';
+ if(isset($lang->{$review->type}->common)) $typeName = $lang->{$review->type}->common;
if($type == 'story') $typeName = $lang->my->auditMenu->audit->story;
+ if($review->type == 'projectreview') $typeName = $lang->project->common;
+ if(isset($flows[$review->type])) $typeName = $flows[$review->type];
- $statusList = $lang->$type->statusList;
+ $statusList = array();
+ if(isset($lang->$type->statusList)) $statusList = $lang->$type->statusList;
if($type == 'attend') $statusList = $lang->attend->reviewStatusList;
+ if(!in_array($type, array('story', 'testcase', 'feedback', 'review')) and strpos(",{$config->my->oaObjectType},", ",$type,") === false)
+ {
+ if($rawMethod == 'audit') $statusList = $lang->approval->nodeList;
+ if(isset($flows[$review->type])) $statusList = $rawMethod == 'audit' ? $lang->approval->nodeList : $lang->approval->statusList;
+ }
?>
id?>
@@ -83,7 +92,8 @@
time?>
$type, 'reviewResultList', array());
+ $reviewResultList = array();
+ if(isset($lang->$type))$reviewResultList = zget($lang->$type, 'reviewResultList', array());
if(strpos(",{$config->my->oaObjectType},", ",$type,") !== false) $reviewResultList = zget($lang->$type, 'reviewStatusList', array());
?>
result);?>
@@ -114,6 +124,10 @@
{
common::printLink($module, 'view', $params, $reviewIcon, '', "class='btn' data-toggle='modal' title='{$lang->review->common}'", true, true);
}
+ elseif(!in_array($module, array('story', 'testcase', 'feedback')))
+ {
+ common::printLink($module, 'approvalreview', $params, $reviewIcon, '', "class='btn' data-toggle='modal' title='{$lang->review->common}'", true, true);
+ }
else
{
common::printLink($module, $method, $params, $reviewIcon, '', "class='btn iframe' title='{$lang->review->common}'", true, true);
diff --git a/module/my/view/requirement.html.php b/module/my/view/requirement.html.php
index 8765bec631..46d5c6bbf9 100644
--- a/module/my/view/requirement.html.php
+++ b/module/my/view/requirement.html.php
@@ -76,7 +76,7 @@
createLink('story', 'view', "id=$story->id");
+ $storyLink = $this->createLink('story', 'view', "id=$story->id&version=0¶m=0&storyType=requirement");
$canBeChanged = common::canBeChanged('story', $story);
$spanClass = $canBatchAction ? 'c-span' : '';
?>
diff --git a/module/product/control.php b/module/product/control.php
index 5f9a78b411..385e17f7e9 100755
--- a/module/product/control.php
+++ b/module/product/control.php
@@ -249,7 +249,7 @@ class product extends control
$showBranch = $this->loadModel('branch')->showBranch($productID);
/* Get stories. */
- if($isProjectStory)
+ if($isProjectStory and $storyType == 'story')
{
$showBranch = $this->loadModel('branch')->showBranch($productID, 0, $projectID);
@@ -257,7 +257,7 @@ class product extends control
$this->products = $this->product->getProducts($projectID, 'all', '', false);
$projectProducts = $this->product->getProducts($projectID);
- $productPlans = $this->execution->getPlans($projectProducts, 'skipParent');
+ $productPlans = $this->execution->getPlans($projectProducts, 'skipParent,unexpired,noclosed', $projectID);
if($browseType == 'bybranch') $param = $branchID;
$stories = $this->story->getExecutionStories($projectID, $productID, $branchID, $sort, $browseType, $param, $storyType, '', $pager);
@@ -274,6 +274,7 @@ class product extends control
if(!$product and $isProjectStory)
{
/* Get branch display under multiple products. */
+ $branchOptions = array();
foreach($projectProducts as $projectProduct)
{
if($projectProduct and $projectProduct->type != 'normal')
@@ -355,7 +356,7 @@ class product extends control
$this->view->productName = $productName;
$this->view->moduleID = $moduleID;
$this->view->stories = $stories;
- $this->view->plans = $this->loadModel('productplan')->getPairs($productID, ($branch === 'all' or empty($branch)) ? '' : $branch, '', true);
+ $this->view->plans = $this->loadModel('productplan')->getPairs($productID, ($branch === 'all' or empty($branch)) ? '' : $branch, 'unexpired,noclosed', true);
$this->view->productPlans = isset($productPlans) ? array(0 => '') + $productPlans : array();
$this->view->summary = $this->product->summary($stories, $storyType);
$this->view->moduleTree = $moduleTree;
@@ -923,6 +924,22 @@ class product extends control
}
}
+ /**
+ * Ajax get product by id.
+ *
+ * @param int $productID
+ * @access public
+ * @return void
+ */
+ public function ajaxGetProductById($productID)
+ {
+ $product = $this->product->getById($productID);
+
+ $product->branchSourceName = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
+ $product->branchName = $this->lang->product->branchName[$product->type];
+ echo json_encode($product);
+ }
+
/**
* AJAX: get projects of a product in html select.
*
@@ -1037,12 +1054,19 @@ class product extends control
public function ajaxGetPlans($productID, $branch = 0, $planID = 0, $fieldID = '', $needCreate = false, $expired = '', $param = '')
{
$param = strtolower($param);
- $plans = $this->loadModel('productplan')->getPairs($productID, $branch, $expired, strpos($param, 'skipparent') !== false);
- $field = $fieldID ? "plans[$fieldID]" : 'plan';
+ if(strpos($param, 'forstory') === false)
+ {
+ $plans = $this->loadModel('productplan')->getPairs($productID, empty($branch) ? 'all' : $branch, $expired, strpos($param, 'skipparent') !== false);
+ }
+ else
+ {
+ $plans = $this->loadModel('productplan')->getPairsForStory($productID, $branch == '0' ? 'all' : $branch, $param);
+ }
+ $field = $fieldID !== '' ? "plans[$fieldID]" : 'plan';
$multiple = strpos($param, 'multiple') === false ? '' : 'multiple';
$output = html::select($field, $plans, $planID, "class='form-control chosen' $multiple");
- if($branch == 0 and strpos($param, 'edit')) $output = html::select($field, $plans, $planID, "class='form-control chosen' multiple");
+ if($branch == 0 and strpos($param, 'edit') and (strpos($param, 'forstory') === false)) $output = html::select($field, $plans, $planID, "class='form-control chosen' multiple");
if(count($plans) == 1 and $needCreate and $needCreate !== 'false')
{
diff --git a/module/product/css/browse.css b/module/product/css/browse.css
index 686cbd21c5..75f034788f 100644
--- a/module/product/css/browse.css
+++ b/module/product/css/browse.css
@@ -41,3 +41,4 @@ a.removeModule:hover {color: red;}
#productStoryForm tbody tr td .label {max-width: 100px; text-overflow: unset;}
.c-span {margin-left: 19px;}
.btn-toolbar #query span.text{overflow: hidden; width: 52px; vertical-align: middle; margin-right: 1px;}
+#linkStoryByPlan .modal-header {padding: 20px 0 10px 0; border-bottom: 0px}
\ No newline at end of file
diff --git a/module/product/css/roadmap.css b/module/product/css/roadmap.css
index 9bef202959..a3746e142b 100644
--- a/module/product/css/roadmap.css
+++ b/module/product/css/roadmap.css
@@ -8,3 +8,4 @@
.release-line > li:nth-child(even) > a {padding-bottom: 38px;}
.release-link-line {pointer-events: none; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA+YAAADiCAMAAAD0xy2eAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAABOUExURUdwTOXl5eXl5ebm5ufn5+rq6u7u7unp6eXl5f///+Xl5eXl5efn5+Xl5eXl5eXl5ebm5uXl5eXl5ebm5ubm5ubm5uXl5ebm5uXl5eXl5Uh6G7kAAAAZdFJOUwDuuVQtFQwg1gXH+Dpzoq1CmIV7aEuOX+MTWsN0AAAGrUlEQVQYGe3ACbaYOHRF0SuQkETfw5v/RPNdLpMZZKWez5buVQBcu8zmJACO1WOwcAqAZ7kx66MAeNa+ZncnAI51t1kvAK7F/hEAAAAAAACA/xP93gmAZ6tZOKsAODb2ZuUSAM/aYtZEAXCsTsFs7gTAse4eigD4lqMAAAAAAMD/D09/CYBrvVl/CYBjdXrN+lUAHOumYNavAuBYNwWzTQA866ZwCYBvnQAAAAD8V7RHEgDParHhzgLg2NqYDXMUAMfiPJg1qwA4lu/BbBMAz7rjvQTAtyoAAOBG1wmAb1O4swB4NptZcwmAY3EbzN4pCYBfaSpmwywAnl2NNQLgW44CAABuZAHwLQ1lSgLg2BjMhuUSAL9q25jZu2cB8CsfxcweAfBsnYcsAL51AgAAboxL2wmAZ5vZMF9VANxKZ29mYRsFwK98FDN7swA4Fvc3VAHwLQsAALgxlSMKgGeNmZU9CoBb9dqCmb33KABu1XV7zSwJgGfjPQsAALixHlEAXJvN3u3qBMCtdXvNbGjOLABuxaO3H1EAHEvPUgQAANwYlycLgGe7mZX76gTAq3wuwcyGZsoC4FUd997MLgHwLLVbJwB/j7UTANeyDf2+dgLgVuwHMxv6fa0C4FR37b39WATAse66+0kA/h45CYBvjZX5yQLg1xLsR1imsQqATzVOy2s/kgA4lp+tEYC/R9rOsRMAx1r7UebjygLgU57mMtgvswC4VeNzN++hT0oC4FDVZ7ehLPe55ioAPu3BfhseAXCqi+2xNa9d+rTnFTsB8KLqH7Xq09iPoTTzngXApXNrSrBfRn1i7ATAlS5e5570KWbD2y/b8XQC4NJSgv2W9RljrgLgR5fH9tznqk8ws1D6ZTs6AXCp6d/B/lH1Oc52jakKgBcpj+156FPtt1B6AXCp27elKcHMgj5d6Zdtn55rzALgRcpRn2x/vPrUMaYqAP9hVZ8a1/Y87rnpG32y/Qhv38y7ALiU+xIG+6Xok5r5Ps52jbkTABe6PF5Pq89ofxR9upg7AXAiXc+0b0tT3kaf0cyGtzTLdgqAS2N5B/tHo09ctn162jGmKgAedDmu7Xnp09ofjT5dTFUAnEjtedzz0pd30+cxs/D2zXy3AuBSW4L9tumzzvdxtmvMnQC40OXxes5Vn8n+2PTpkgC4EZ9p35amvMOuz2QWSr/M9zQKgEtTsH/t+qzH2a4xVQFwoaY4Xs+0r/rs9lsopwC4tO7b0pcwmJ367KVftn16rjEJgBddTvrM9sejT46pCoATKY7tedxz06/6zGYWSr9sexQAl+4S7LdLn3Z6rpirAHiR4tieR9ansX+E0o8C4NJzz015BzMb9bmX7TjbMXcC4EaXx06fYv8aogC4NLbnvi19CZb06Usz38dzjVkA/Kj6X8H+SPpUAfAjxfWZ9rnp36pPCKWZ7+m5ogC4VAf7o+rTCYAjKa7PdM9Nr0+1UJr5ntoxC4BLebB/BQFwKsX1me6lb/RJ89HGKgB+jfZLae5zFACX0nk3xX5pBMCvGttjnvRJWQB8myw0e5sFwK0p2C+h2aMAOJXbvQlm1gqAZ/k6sgD8PfpmXzsB8KuzX8rWZgFwKrV3P9iPIgB+1fFoQi8AziV9qgA4d4bljALg2GY/3vnJAuBVfubXfryrAPgVzyVYFgDfogD8Per8JAHwbDWzfh+rAHiVpmYws7CcWQC8qtddzOwWAM/yuawCAABu9HPbCYBj2cyG5swC4FacevtR9lEA3ErPEswWAfCsrncrAADgxraPAuBZN5i921oFwK31fs0szG0nAG7FvZhZLwCe5ak/BAAA3FizAPj2WjmyAPjVzcHMyhEFwK16zcHMyiQAftVrCzYLgGt1jQIAAG7MRxYAz7KZ9VMWALfqNQcz66ckAG7VdhnM7BYAx2q7DKcA+NZ1AgAAXtSyjQLg2WVm7x4FwK94v2ZWpiwAfo1bMLNJAByr1zyMAuBbJwAA4EYsUxYAzw4za55OANyq1zKYDctVBcCt7mnMLIwC4Fg+ytAJgG9ZAADAjWuPAuBaY9afSQD8GrdgNixXFQC3atuYWbirAPiVpmJFAHyLqwAAgBtjEgDX6jssVxUAv9IymIU7CoBfaSpm1p9JAPwat2A2C4BntW1WAQAAAPiPyGVKAuDZYTYslwD4VdvGzN49C4Bf6Shm1kQBcGydhyEJgGvdJQAA4EZOAuDbMiyrAHi2mNl7ZAHwKx2vmTVXFQC/1mUw6wXAszSVQwCcqwIAAG4cSQBcW4ckAK7lU/8D/a9fSZZlwxkAAAAASUVORK5CYII="); height: 196px; position: absolute; left: 10px; right: 100px; top: 97px; background-size: 100% 100%; background-repeat: no-repeat;}
.release-line li .title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
+.branch-nav .nav li { max-width: 200px; white-space: nowrap; overflow: hidden; }
diff --git a/module/product/model.php b/module/product/model.php
index 8523500b68..1ae9044709 100644
--- a/module/product/model.php
+++ b/module/product/model.php
@@ -1578,10 +1578,16 @@ class productModel extends model
if($plan->parent > 0 and isset($parents[$plan->parent])) $plan->title = $parents[$plan->parent] . ' / ' . $plan->title;
$year = substr($plan->end, 0, 4);
- $roadmap[$year][$plan->branch][] = $plan;
+ $branchIdList = explode(',', trim($plan->branch, ','));
+ $branchIdList = array_unique($branchIdList);
+ foreach($branchIdList as $branchID)
+ {
+ if($branchID === '') continue;
+ $roadmap[$year][$branchID][] = $plan;
+ }
$total++;
- if($count > 0 and $total >= $count) return $this->processRoadmap($roadmap);
+ if($count > 0 and $total >= $count) return $this->processRoadmap($roadmap, $branch);
}
}
@@ -1595,14 +1601,20 @@ class productModel extends model
foreach($releases as $release)
{
$year = substr($release->date, 0, 4);
- $roadmap[$year][$release->branch][] = $release;
+ $branchIdList = explode(',', trim($release->branch, ','));
+ $branchIdList = array_unique($branchIdList);
+ foreach($branchIdList as $branchID)
+ {
+ if($branchID === '') continue;
+ $roadmap[$year][$branchID][] = $release;
+ }
$total++;
- if($count > 0 and $total >= $count) return $this->processRoadmap($roadmap);
+ if($count > 0 and $total >= $count) return $this->processRoadmap($roadmap, $branch);
}
}
- if($count > 0) return $this->processRoadmap($roadmap);
+ if($count > 0) return $this->processRoadmap($roadmap, $branch);
$groupRoadmap = array();
foreach($roadmap as $year => $branchRoadmaps)
@@ -1643,16 +1655,18 @@ class productModel extends model
* Process roadmap.
*
* @param array $roadmap
+ * @param int $branch
* @access public
* @return array
*/
- public function processRoadmap($roadmapGroups)
+ public function processRoadmap($roadmapGroups, $branch)
{
$newRoadmap = array();
foreach($roadmapGroups as $year => $branchRoadmaps)
{
- foreach($branchRoadmaps as $branch => $roadmaps)
+ foreach($branchRoadmaps as $branchID => $roadmaps)
{
+ if($branch != $branchID) continue;
foreach($roadmaps as $roadmap) $newRoadmap[] = $roadmap;
}
}
diff --git a/module/product/view/browse.html.php b/module/product/view/browse.html.php
index dffefb71c9..f6440c871c 100755
--- a/module/product/view/browse.html.php
+++ b/module/product/view/browse.html.php
@@ -620,7 +620,11 @@ js::set('vision', $this->config->vision);
diff --git a/module/productplan/config.php b/module/productplan/config.php
index 4dddb53677..bd16b51610 100644
--- a/module/productplan/config.php
+++ b/module/productplan/config.php
@@ -30,7 +30,7 @@ $config->productplan->search['fields']['end'] = $lang->productplan->end;
$config->productplan->search['params']['id'] = array('operator' => '=', 'control' => 'input', 'values' => '');
$config->productplan->search['params']['title'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
-$config->productplan->search['params']['branch'] = array('operator' => '=', 'control' => 'select', 'values' => '');
+$config->productplan->search['params']['branch'] = array('operator' => 'include', 'control' => 'select', 'values' => '');
$config->productplan->search['params']['status'] = array('operator' => '=', 'control' => 'select', 'values' => array('' => '') + $lang->productplan->statusList);
$config->productplan->search['params']['begin'] = array('operator' => '=', 'control' => 'input', 'values' => '', 'class' => 'date');
$config->productplan->search['params']['end'] = array('operator' => '=', 'control' => 'input', 'values' => '', 'class' => 'date');
diff --git a/module/productplan/control.php b/module/productplan/control.php
index cce8f8b09d..8a941102e7 100644
--- a/module/productplan/control.php
+++ b/module/productplan/control.php
@@ -84,7 +84,7 @@ class productplan extends control
}
$this->commonAction($productID, $branchID);
- $lastPlan = $this->productplan->getLast($productID, $branchID, $parent);
+ $lastPlan = $this->productplan->getLast($productID, '', $parent);
$product = $this->loadModel('product')->getById($productID);
if($lastPlan)
@@ -117,7 +117,7 @@ class productplan extends control
$this->view->branches = $branchPairs;
$this->view->defaultBranch = $defaultBranch;
$this->view->parent = $parent;
- $this->view->parentPlanPairs = $this->productplan->getTopPlanPairs($productID, $branchID, 'done,closed');
+ $this->view->parentPlanPairs = $this->productplan->getTopPlanPairs($productID, 'done,closed');
$this->display();
}
@@ -136,7 +136,7 @@ class productplan extends control
$changes = $this->productplan->update($planID);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$change[$planID] = $changes;
- $this->syncStory($change);
+ $this->unlinkOldBranch($change);
if($changes)
{
$actionID = $this->loadModel('action')->create('productplan', $planID, 'edited');
@@ -151,11 +151,19 @@ class productplan extends control
$oldBranch = array($planID => $plan->branch);
/* Get the parent plan pair exclusion itself. */
- $parentPlanPairs = $this->productplan->getTopPlanPairs($plan->product, $plan->branch);
+ $parentPlanPairs = $this->productplan->getTopPlanPairs($plan->product);
unset($parentPlanPairs[$planID]);
$this->view->parentPlanPairs = $parentPlanPairs;
$this->commonAction($plan->product, $plan->branch);
+
+ if($plan->parent > 0)
+ {
+ $parentPlan = $this->productplan->getByID($plan->parent);
+ $branchPairs = array();
+ foreach(explode(',', $parentPlan->branch) as $parentBranchID) $branchPairs[$parentBranchID] = $this->view->branchTagOption[$parentBranchID];
+ $this->view->branchTagOption = $branchPairs;
+ }
$this->view->title = $this->view->product->name . $this->lang->colon . $this->lang->productplan->edit;
$this->view->position[] = $this->lang->productplan->edit;
$this->view->productID = $plan->product;
@@ -178,7 +186,7 @@ class productplan extends control
if(!empty($_POST['title']))
{
$changes = $this->productplan->batchUpdate($productID);
- $this->syncStory($changes);
+ $this->unlinkOldBranch($changes);
$this->loadModel('action');
foreach($changes as $planID => $change)
{
@@ -194,15 +202,21 @@ class productplan extends control
$this->commonAction($productID, $branch);
- $plans = $this->productplan->getByIDList($this->post->planIDList);
- $oldBranch = array();
+ $plans = $this->productplan->getByIDList($this->post->planIDList);
+ $oldBranch = array();
+ $parentIdList = array();
- foreach($plans as $plan) $oldBranch[$plan->id] = $plan->branch;
+ foreach($plans as $plan)
+ {
+ $oldBranch[$plan->id] = $plan->branch;
+ $parentIdList[$plan->parent] = $plan->parent;
+ }
- $this->view->title = $this->lang->productplan->batchEdit;
- $this->view->plans = $plans;
- $this->view->oldBranch = $oldBranch;
- $this->view->product = $this->loadModel('product')->getById($productID);;
+ $this->view->title = $this->lang->productplan->batchEdit;
+ $this->view->plans = $plans;
+ $this->view->oldBranch = $oldBranch;
+ $this->view->product = $this->loadModel('product')->getById($productID);;
+ $this->view->parentList = $this->productplan->getByIDList($parentIdList);
$this->display();
}
@@ -447,8 +461,6 @@ class productplan extends control
if($plan->parent > 0) $this->view->parentPlan = $this->productplan->getById($plan->parent);
if($plan->parent == '-1') $this->view->childrenPlans = $this->productplan->getChildren($plan->id);
- if($plan->branch > 0) $this->view->branchStatus = $this->loadModel('branch')->getById($plan->branch, $plan->product, 'status');
-
$storyIdList = array();
$modulePairs = $this->loadModel('tree')->getOptionMenu($plan->product, 'story', 0, 'all');
foreach($planStories as $story)
@@ -593,7 +605,7 @@ class productplan extends control
*/
public function ajaxGetProductplans($productID, $branch = 0, $number = '', $expired = '')
{
- $plans = $this->productplan->getPairs($productID, $branch, $expired, true);
+ $plans = $this->productplan->getPairs($productID, empty($branch) ? '' : $branch, $expired, true);
$planName = $number === '' ? 'plan' : "plan[$number]";
$plans = empty($plans) ? array('' => '') : $plans;
echo html::select($planName, $plans, '', "class='form-control'");
@@ -675,7 +687,7 @@ class productplan extends control
$this->config->product->search['style'] = 'simple';
$this->config->product->search['params']['product']['values'] = $products + array('all' => $this->lang->product->allProductsOfProject);
$this->config->product->search['params']['plan']['values'] = $this->productplan->getPairsForStory($plan->product, $plan->branch, 'skipParent|withMainPlan');
- $this->config->product->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($plan->product, 'story', 0, $plan->branch);
+ $this->config->product->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($plan->product, 'story', 0, 'all');
$storyStatusList = $this->lang->story->statusList;
unset($storyStatusList['closed']);
$this->config->product->search['params']['status'] = array('operator' => '=', 'control' => 'select', 'values' => $storyStatusList);
@@ -687,8 +699,9 @@ class productplan extends control
else
{
$this->config->product->search['fields']['branch'] = $this->lang->product->branch;
- $branchName = $this->loadModel('branch')->getById($plan->branch);
- $branches = array('' => '', BRANCH_MAIN => $this->lang->branch->main, $plan->branch => $branchName);
+
+ $branchPairs = $this->dao->select('id, name')->from(TABLE_BRANCH)->where('id')->in($plan->branch)->fetchPairs();
+ $branches = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branchPairs;
$this->config->product->search['params']['branch']['values'] = $branches;
}
$this->loadModel('search')->setSearchParams($this->config->product->search);
@@ -809,9 +822,12 @@ class productplan extends control
$this->config->bug->search['style'] = 'simple';
$this->config->bug->search['params']['plan']['values'] = $this->productplan->getPairsForStory($productID, $plan->branch, 'skipParent|withMainPlan');
$this->config->bug->search['params']['execution']['values'] = $this->loadModel('product')->getExecutionPairsByProduct($plan->product, $plan->branch);
+ $this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs($productID, $branch = 'all', $params = '');
+ $this->config->bug->search['params']['resolvedBuild']['values'] = $this->build->getBuildPairs($productID, $branch = 'all', $params = '');
+ $this->config->bug->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($plan->product, 'bug', 0, 'all');
$this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs($productID, $branch = 'all', $params = 'releasetag');
$this->config->bug->search['params']['resolvedBuild']['values'] = $this->config->bug->search['params']['openedBuild']['values'];
- $this->config->bug->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($plan->product, 'bug', 0, $plan->branch);
+ $this->config->bug->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($plan->product, 'bug', 0, 'all');
$this->config->bug->search['params']['project']['values'] = $this->product->getProjectPairsByProduct($productID, $plan->branch);
unset($this->config->bug->search['fields']['product']);
@@ -823,8 +839,9 @@ class productplan extends control
else
{
$this->config->bug->search['fields']['branch'] = $this->lang->product->branch;
- $branchName = $this->loadModel('branch')->getById($plan->branch);
- $branches = array('' => '', BRANCH_MAIN => $this->lang->branch->main, $plan->branch => $branchName);
+
+ $branchPairs = $this->dao->select('id, name')->from(TABLE_BRANCH)->where('id')->in($plan->branch)->fetchPairs();
+ $branches = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branchPairs;
$this->config->bug->search['params']['branch']['values'] = $branches;
}
$this->loadModel('search')->setSearchParams($this->config->bug->search);
@@ -891,13 +908,13 @@ class productplan extends control
}
/**
- * Synchronize story when edit plan.
+ * Unlink story and bug when edit branch of plan.
* @param int $planID
* @param int $oldBranch
* @access protected
* @return void
*/
- protected function syncStory($changes)
+ protected function unlinkOldBranch($changes)
{
foreach($changes as $planID => $changes)
{
@@ -913,38 +930,71 @@ class productplan extends control
}
}
$planStories = $this->loadModel('story')->getPlanStories($planID, 'all');
+ $planBugs = $this->loadModel('bug')->getPlanBugs($planID, 'all');
if($oldBranch)
{
foreach($planStories as $storyID => $story)
{
- if($story->branch and $story->branch != $newBranch) $this->productplan->unlinkStory($storyID, $planID);
+ if($story->branch and strpos(",$newBranch,", ",$story->branch,") === false) $this->productplan->unlinkStory($storyID, $planID);
+ }
+
+ foreach($planBugs as $bugID => $bug)
+ {
+ if($bug->branch and strpos(",$newBranch,", ",$bug->branch,") === false) $this->productplan->unlinkBug($bugID, $planID);
}
}
}
}
/**
- * AJAX: Get conflict story.
+ * AJAX: Get conflict story and bug.
*
* @param int $planID
* @param int $branch
* @access public
* @return void
*/
- public function ajaxGetConflictStory($planID, $newBranch)
+ public function ajaxGetConflict($planID, $newBranch)
{
- $plan = $this->productplan->getByID($planID);
- $oldBranch = $plan->branch;
- $planStories = $this->loadModel('story')->getPlanStories($planID, 'all');
- $conflictStoryIdList = '';
+ $plan = $this->productplan->getByID($planID);
+ $oldBranch = $plan->branch;
+ $planStories = $this->loadModel('story')->getPlanStories($planID, 'all');
+ $planBugs = $this->loadModel('bug')->getPlanBugs($planID, 'all');
+ $branchPairs = $this->loadModel('branch')->getPairs($plan->product);
+
+ $removeBranches = '';
+ foreach(explode(',', $oldBranch) as $oldBranchID)
+ {
+ if($oldBranchID and strpos(",$newBranch,", ",$oldBranchID,") === false) $removeBranches .= "{$branchPairs[$oldBranchID]},";
+ }
+
+ $conflictStoryCounts = 0;
+ $conflictBugCounts = 0;
if($oldBranch)
{
foreach($planStories as $storyID => $story)
{
- if($story->branch and $story->branch != $newBranch) $conflictStoryIdList .= '[' . $storyID . ']';
+ if($story->branch and strpos(",$newBranch,", ",$story->branch,") === false) $conflictStoryCounts ++;
+ }
+
+ foreach($planBugs as $bugID => $bug)
+ {
+ if($bug->branch and strpos(",$newBranch,", ",$bug->branch,") === false) $conflictBugCounts ++;
}
}
- if($conflictStoryIdList != '') printf($this->lang->story->confirmChangePlan, $conflictStoryIdList);
+
+ if($conflictStoryCounts and $conflictBugCounts)
+ {
+ printf($this->lang->productplan->confirmChangePlan, trim($removeBranches, ','), $conflictStoryCounts, $conflictBugCounts);
+ }
+ elseif($conflictStoryCounts)
+ {
+ printf($this->lang->productplan->confirmRemoveStory, trim($removeBranches, ','), $conflictStoryCounts);
+ }
+ elseif($conflictBugCounts)
+ {
+ printf($this->lang->productplan->confirmRemoveBug, trim($removeBranches, ','), $conflictBugCounts);
+ }
}
/**
@@ -963,16 +1013,70 @@ class productplan extends control
}
/**
- * AJAX: Get top plan.
+ * AJAX: Get parent branches.
*
* @param int $productID
- * @param int $branch
+ * @param int $parentID
+ * @param string $currentBranches
* @access public
- * @return object
+ * @return void
*/
- public function ajaxGetTopPlan($productID, $branch = 0)
+ public function ajaxGetParentBranches($productID = 0, $parentID = 0, $currentBranches = '')
{
- $parentPlanPairs = $this->productplan->getTopPlanPairs($productID, $branch);
- return print(html::select('parent', array('0' => '') + $parentPlanPairs, '', 'class="form-control"'));
+ $branchPairs = $this->loadModel('branch')->getPairs($productID, 'active');
+ if(!empty($parentID))
+ {
+ $parentBranches = array();
+ $parentPlan = $this->productplan->getByID($parentID);
+ foreach(explode(',', $parentPlan->branch) as $parentBranchID)
+ {
+ if(!isset($branchPairs[$parentBranchID])) continue;
+ $parentBranches[$parentBranchID] = $branchPairs[$parentBranchID];
+ if(!empty($currentBranches) and strpos(",$currentBranches,", ",$parentBranchID,") === false) $currentBranches = str_replace(",$parentBranchID,", ',', $currentBranches);
+ }
+ }
+ return print(html::select('branch[]', empty($parentID) ? $branchPairs : $parentBranches, trim($currentBranches, ','), "class='form-control chosen' multiple"));
+ }
+
+ /**
+ * AJAX: Get diff branches tips.
+ *
+ * @param int $productID
+ * @param int $parentID
+ * @param string $branches
+ * @access public
+ * @return void
+ */
+ public function ajaxGetDiffBranchesTip($productID = 0, $parentID = 0, $branches = '')
+ {
+ if(empty($parentID) or empty($productID)) return;
+
+ /* If it has children, return. */
+ $parentBranch = $this->productplan->getByID($parentID);
+ if($parentBranch->parent == '-1') return;
+
+ /* Find diff branches between parent plan and child plan. */
+ $diffBranches = array();
+ $diffBranchesTip = '';
+ $product = $this->loadModel('product')->getByID($productID);
+ $branchPairs = $this->loadModel('branch')->getPairs($productID);
+ foreach(explode(',', $parentBranch->branch) as $parentBranchID)
+ {
+ if(empty($parentBranchID)) continue;
+ if(strpos(",$branches,", ",$parentBranchID,") === false)
+ {
+ $diffBranches[$parentBranchID] = $parentBranchID;
+ $diffBranchesTip .= "{$branchPairs[$parentBranchID]},";
+ }
+ }
+ if(empty($diffBranchesTip)) return;
+
+ /* Find stories and bugs in diff branches. */
+ $unlinkStories = $this->dao->select('*')->from(TABLE_STORY)->where('branch')->in($diffBranches)->andWhere("CONCAT(',', plan, ',')")->like("%,{$parentID},%")->fetchAll('id');
+ $unlinkBugs = $this->dao->select('*')->from(TABLE_BUG)->where('branch')->in($diffBranches)->andWhere('plan')->eq($parentID)->fetchAll('id');
+ if(empty($unlinkStories) and empty($unlinkBugs)) return;
+
+ $this->lang->productplan->diffBranchesTip = str_replace('@branch@', $this->lang->product->branchName[$product->type], $this->lang->productplan->diffBranchesTip);
+ printf($this->lang->productplan->diffBranchesTip, trim($diffBranchesTip, ','));
}
}
diff --git a/module/productplan/css/browse.css b/module/productplan/css/browse.css
index 0c62007c4c..ae54b036db 100644
--- a/module/productplan/css/browse.css
+++ b/module/productplan/css/browse.css
@@ -1,7 +1,7 @@
.table td.content div {height: 25px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; float: left; max-width: calc(100% - 20px);}
.table td.content .more {position: absolute; right: 16px;}
.table td.content .more .icon {color: #838a9d;}
-td.c-branch {overflow: hidden; text-align: left !important; text-overflow: ellipsis; white-space: nowrap;}
+td.c-branch {overflow: hidden; text-align: left !important; white-space: nowrap;}
.table-children {border-left: 2px solid #cbd0db; border-right: 2px solid #cbd0db;}
.table tbody > tr.table-children.table-child-top {border-top: 2px solid #cbd0db;}
@@ -17,11 +17,12 @@ td.c-branch {overflow: hidden; text-align: left !important; text-overflow: ellip
@-moz-document url-prefix() {.main-table tbody > tr.table-children > td:first-child::before {width: 4px;};}
.c-title {width: 160px;}
-.c-branch {width: 100px;}
+.c-branch {width: 130px;}
.c-story, .c-status {width: 80px;}
.c-execution {width: 60px !important;}
.c-bug, .c-hour {width: 60px;}
-.c-desc {width: 160px;}
+.c-desc {width: 140px;}
+.c-date {width: 90px;}
.plan-name {position: relative; display: flex; align-items: center;}
.plan-name > span {flex: none;}
diff --git a/module/productplan/css/view.css b/module/productplan/css/view.css
index cffbb1ac2d..b924713e90 100644
--- a/module/productplan/css/view.css
+++ b/module/productplan/css/view.css
@@ -33,3 +33,6 @@ ol, ul {padding-left: 20px;}
#mainMenu .btn-toolbar .page-title .text {max-width: 434px; overflow: hidden; white-space:nowrap; margin-right: 6px;}
#actionsBox a:last-of-type + .divider {display: none;}
#actionsBox div.divider:first-child {display: none;}
+
+#bugs th.c-pri, #bugs th.c-actions {width:70px !important;}
+#bugs th.c-status, #bugs th.c-user {width:120px !important;}
diff --git a/module/productplan/js/batchedit.js b/module/productplan/js/batchedit.js
index 247b9b1772..9384b7631a 100644
--- a/module/productplan/js/batchedit.js
+++ b/module/productplan/js/batchedit.js
@@ -24,17 +24,17 @@ function changeDate(planID)
* Get conflict stories.
*
* @param int $planID
- * @param int $branch
* @access public
* @return void
*/
-function getConflictStories(planID, branch)
+function getConflictStories(planID)
{
- $.get(createLink('productplan', 'ajaxGetConflictStory', 'planID=' + planID + '&newBranch=' + branch), function(conflictStories)
+ var newBranch = $('#branch' + planID).val() ? $('#branch' + planID).val().toString() : '';
+ $.get(createLink('productplan', 'ajaxGetConflict', 'planID=' + planID + '&newBranch=' + newBranch), function(conflictStories)
{
if(conflictStories != '' && !confirm(conflictStories))
{
- $('#branch' + planID).val(oldBranch[planID]);
+ $('#branch' + planID).val(oldBranch[planID].split(','));
$('#branch' + planID).trigger("chosen:updated");
}
});
diff --git a/module/productplan/js/create.js b/module/productplan/js/create.js
index bbf0718f74..b7c4c8ae53 100644
--- a/module/productplan/js/create.js
+++ b/module/productplan/js/create.js
@@ -63,23 +63,47 @@ $('#future').on('change', function()
}
});
-$('#branch').change(function()
+$('#parent').change(function()
{
- var branchID = $(this).val();
- var lastPlanLink = createLink('productplan', 'ajaxGetLast', "productID=" + productID + "&branch=" + branchID);
- var topPlanLink = createLink('productplan', 'ajaxGetTopPlan', "productID=" + productID + "&branch=" + branchID);
+ var parentID = $(this).val();
+ var currentBranches = $('#branch').val() ? $('#branch').val().toString() : '';
+ $.post(createLink('productplan', 'ajaxGetParentBranches', "productID=" + productID + "&parentID=" + parentID + "¤tBranches=" + currentBranches), function(data)
+ {
+ $('#branch').replaceWith(data);
+ $('#branch_chosen').remove();
+ $('#branch').chosen();
+ $('#branch').change();
+ })
+})
+$('#dataform').on('change', '#branch', function()
+{
+ if(parentPlanID) return;
+
+ var branchIdList = $(this).val();
+ if(!branchIdList) return;
+
+ var branchIdList = branchIdList.toString();
+ var lastPlanLink = createLink('productplan', 'ajaxGetLast', "productID=" + productID + "&branch=" + branchIdList);
$.post(lastPlanLink, function(data)
{
data = JSON.parse(data);
var planTitle = data ? '(' + lastLang + ': ' + data.title + ')' : '';
$('#title').parent().next('td').html(planTitle);
})
+});
- $.post(topPlanLink, function(data)
+$('#submit').click(function()
+{
+ var parentPlan = $('#parent').val();
+ var branches = $('#branch').val();
+ if(parentPlan > 0 && branches)
{
- $('#parent').replaceWith(data);
- $('#parent_chosen').remove();
- $('#parent').chosen();
- })
+ link = createLink('productplan', 'ajaxGetDiffBranchesTip', "produtID=" + productID + "&parentID=" + parentPlan + "&branches=" + branches.toString());
+ $.post(link, function(diffBranchesTip)
+ {
+ if((diffBranchesTip != '' && confirm(diffBranchesTip)) || !diffBranchesTip) $('form#dataform').submit();
+ });
+ return false;
+ }
});
diff --git a/module/productplan/js/edit.js b/module/productplan/js/edit.js
index 76f811cabf..4d4a4db832 100644
--- a/module/productplan/js/edit.js
+++ b/module/productplan/js/edit.js
@@ -13,40 +13,22 @@ function convertStringToDate(dateString)
return Date.parse(dateString);
}
-/**
- * Get conflict stories.
- *
- * @param int $planID
- * @param int $branch
- * @access public
- * @return void
- */
-function getConflictStories(planID, branch)
+$('#dataform').on('change', '#branch', function()
{
- $.get(createLink('productplan', 'ajaxGetConflictStory', 'planID=' + planID + '&newBranch=' + branch), function(conflictStories)
+ var newBranch = $('#branch').val() ? $('#branch').val().toString() : '';
+ $.get(createLink('productplan', 'ajaxGetConflict', 'planID=' + planID + '&newBranch=' + newBranch), function(conflictStories)
{
if(conflictStories != '')
{
var result = confirm(conflictStories) ? true : false;
if(!result)
{
- $('#branch').val(oldBranch[planID]);
+ $('#branch').val(oldBranch[planID].split(','));
$('#branch').trigger("chosen:updated");
}
}
-
- if(conflictStories == '' || result)
- {
- var link = createLink('productplan', 'ajaxGetTopPlan', "productID=" + productID + "&branch=" + branch);
- $.post(link, function(data)
- {
- $('#parent').replaceWith(data);
- $('#parent_chosen').remove();
- $('#parent').chosen();
- })
- }
});
-}
+});
/**
* Compute the end date for productplan.
@@ -74,6 +56,18 @@ function computeEndDate(delta)
$('#end').val(endDate).datetimepicker('update');
}
+$('#parent').change(function()
+{
+ var parentID = $(this).val();
+ var currentBranches = $('#branch').val() ? $('#branch').val().toString() : '';
+ $.post(createLink('productplan', 'ajaxGetParentBranches', "productID=" + productID + "&parentID=" + parentID + "¤tBranches=" + currentBranches), function(data)
+ {
+ $('#branch').replaceWith(data);
+ $('#branch_chosen').remove();
+ $('#branch').chosen();
+ })
+})
+
$('#future').on('change', function()
{
if($(this).prop('checked'))
@@ -88,5 +82,19 @@ $('#future').on('change', function()
}
});
-
$('#future').change();
+
+$('#submit').click(function()
+{
+ var parentPlan = $('#parent').val();
+ var branches = $('#branch').val();
+ if(parentPlan > 0 && branches)
+ {
+ link = createLink('productplan', 'ajaxGetDiffBranchesTip', "produtID=" + productID + "&parentID=" + parentPlan + "&branches=" + branches.toString());
+ $.post(link, function(diffBranchesTip)
+ {
+ if((diffBranchesTip != '' && confirm(diffBranchesTip)) || !diffBranchesTip) $('form#dataform').submit();
+ });
+ return false;
+ }
+});
diff --git a/module/productplan/lang/de.php b/module/productplan/lang/de.php
index 0776e1a3be..1e77f9e257 100644
--- a/module/productplan/lang/de.php
+++ b/module/productplan/lang/de.php
@@ -75,6 +75,9 @@ $lang->productplan->projectNotEmpty = 'Project cannot be empty.';
$lang->productplan->nextStep = "Next step";
$lang->productplan->summary = "Total:
%s , Parents:
%s , Children:
%s ,Independent:
%s .";
$lang->productplan->checkedSummary = "Seleted:
%total% , Parents:
%parent% , Children:
%child% , Independent:
%independent% .";
+$lang->productplan->confirmChangePlan = "After the branch of『%s』is unlinked, %s {$lang->SRCommon} and %s bugs under the branch will be removed from the plan at the same time, so still want to unassociate?";
+$lang->productplan->confirmRemoveStory = "After the branch of『%s』is unlinked, %s {$lang->SRCommon} under the branch will be removed from the plan at the same time, so still want to unassociate?";
+$lang->productplan->confirmRemoveBug = "After the branch of『%s』is unlinked, %s bugs under the branch will be removed from the plan at the same time, so still want to unassociate?";
$lang->productplan->id = 'ID';
$lang->productplan->product = $lang->productCommon;
@@ -97,7 +100,6 @@ $lang->productplan->childrenAB = "C";
$lang->productplan->order = "Rank";
$lang->productplan->deleted = "Deleted";
$lang->productplan->mailto = "Mailto";
-$lang->productplan->status = 'Status';
$lang->productplan->planStatus = "Status";
$lang->productplan->statusList['wait'] = 'Wait';
@@ -136,6 +138,8 @@ $lang->productplan->beginGreaterChildTip = "The start date of the parent plan[%s
$lang->productplan->endLetterChildTip = "The completion date of the parent plan[%s]: %s, cannot be less than the completion date of the child plan: %s.";
$lang->productplan->beginLetterParentTip = "The start date of the child plan[%s]: %s, cannot be less than the start date of the parent plan: %s.";
$lang->productplan->endGreaterParentTip = "The completion date of the child plan[%s]: %s, cannot be greater than the completion date of the parent plan: %s.";
+$lang->productplan->diffBranchesTip = "The @branch@『%s』 of parent plan is not linked with the child plan. @branch@'s stories and bugs whill be removed from the plan. Do you want to save?";
+$lang->productplan->deleteBranchTip = "@branch@『%s』are linked with sub plans and cannot be modified.";
$lang->productplan->featureBar['browse']['all'] = 'Alle';
$lang->productplan->featureBar['browse']['undone'] = 'Undone';
diff --git a/module/productplan/lang/en.php b/module/productplan/lang/en.php
index 41b5fe348c..65233b5eb2 100644
--- a/module/productplan/lang/en.php
+++ b/module/productplan/lang/en.php
@@ -75,6 +75,9 @@ $lang->productplan->projectNotEmpty = 'Project cannot be empty.';
$lang->productplan->nextStep = "Next step";
$lang->productplan->summary = "Total:
%s , Parents:
%s , Children:
%s ,Independent:
%s .";
$lang->productplan->checkedSummary = "Seleted:
%total% , Parents:
%parent% , Children:
%child% , Independent:
%independent% .";
+$lang->productplan->confirmChangePlan = "After the branch of『%s』is unlinked, %s {$lang->SRCommon} and %s bugs under the branch will be removed from the plan at the same time, so still want to unassociate?";
+$lang->productplan->confirmRemoveStory = "After the branch of『%s』is unlinked, %s {$lang->SRCommon} under the branch will be removed from the plan at the same time, so still want to unassociate?";
+$lang->productplan->confirmRemoveBug = "After the branch of『%s』is unlinked, %s bugs under the branch will be removed from the plan at the same time, so still want to unassociate?";
$lang->productplan->id = 'ID';
$lang->productplan->product = $lang->productCommon;
@@ -97,7 +100,6 @@ $lang->productplan->childrenAB = "C";
$lang->productplan->order = "Order";
$lang->productplan->deleted = "Deleted";
$lang->productplan->mailto = "Mailto";
-$lang->productplan->status = "Plan Status";
$lang->productplan->planStatus = "Status";
$lang->productplan->statusList['wait'] = 'Wait';
@@ -136,6 +138,8 @@ $lang->productplan->beginGreaterChildTip = "The start date of the parent plan[%s
$lang->productplan->endLetterChildTip = "The completion date of the parent plan[%s]: %s, cannot be less than the completion date of the child plan: %s.";
$lang->productplan->beginLetterParentTip = "The start date of the child plan[%s]: %s, cannot be less than the start date of the parent plan: %s.";
$lang->productplan->endGreaterParentTip = "The completion date of the child plan[%s]: %s, cannot be greater than the completion date of the parent plan: %s.";
+$lang->productplan->diffBranchesTip = "The @branch@『%s』of parent plan is not linked with the child plan. @branch@'s stories and bugs whill be removed from the plan. Do you want to save?";
+$lang->productplan->deleteBranchTip = "@branch@『%s』are linked with sub plans and cannot be modified.";
$lang->productplan->featureBar['browse']['all'] = 'All';
$lang->productplan->featureBar['browse']['undone'] = 'Undone';
diff --git a/module/productplan/lang/fr.php b/module/productplan/lang/fr.php
index dc5390ac80..263bb511a7 100644
--- a/module/productplan/lang/fr.php
+++ b/module/productplan/lang/fr.php
@@ -75,6 +75,9 @@ $lang->productplan->projectNotEmpty = 'Project cannot be empty.';
$lang->productplan->nextStep = "Next step";
$lang->productplan->summary = "Total:
%s , Parents:
%s , Children:
%s ,Independent:
%s .";
$lang->productplan->checkedSummary = "Seleted:
%total% , Parents:
%parent% , Children:
%child% , Independent:
%independent% .";
+$lang->productplan->confirmChangePlan = "After the branch of『%s』is unlinked, %s {$lang->SRCommon} and %s bugs under the branch will be removed from the plan at the same time, so still want to unassociate?";
+$lang->productplan->confirmRemoveStory = "After the branch of『%s』is unlinked, %s {$lang->SRCommon} under the branch will be removed from the plan at the same time, so still want to unassociate?";
+$lang->productplan->confirmRemoveBug = "After the branch of『%s』is unlinked, %s bugs under the branch will be removed from the plan at the same time, so still want to unassociate?";
$lang->productplan->id = 'ID';
$lang->productplan->product = $lang->productCommon;
@@ -97,7 +100,6 @@ $lang->productplan->childrenAB = "C";
$lang->productplan->order = "Order";
$lang->productplan->deleted = "Deleted";
$lang->productplan->mailto = "Mailto";
-$lang->productplan->status = 'Status';
$lang->productplan->planStatus = "Status";
$lang->productplan->statusList['wait'] = 'Wait';
@@ -136,6 +138,8 @@ $lang->productplan->beginGreaterChildTip = "The start date of the parent plan[%s
$lang->productplan->endLetterChildTip = "The completion date of the parent plan[%s]: %s, cannot be less than the completion date of the child plan: %s.";
$lang->productplan->beginLetterParentTip = "The start date of the child plan[%s]: %s, cannot be less than the start date of the parent plan: %s.";
$lang->productplan->endGreaterParentTip = "The completion date of the child plan[%s]: %s, cannot be greater than the completion date of the parent plan: %s.";
+$lang->productplan->diffBranchesTip = "The @branch@『%s』of parent plan is not linked with the child plan. @branch@'s stories and bugs whill be removed from the plan. Do you want to save?";
+$lang->productplan->deleteBranchTip = "@branch@『%s』are linked with sub plans and cannot be modified.";
$lang->productplan->featureBar['browse']['all'] = 'Tous';
$lang->productplan->featureBar['browse']['undone'] = 'Undone';
diff --git a/module/productplan/lang/zh-cn.php b/module/productplan/lang/zh-cn.php
index 119714e9de..b756ef52d2 100644
--- a/module/productplan/lang/zh-cn.php
+++ b/module/productplan/lang/zh-cn.php
@@ -75,6 +75,9 @@ $lang->productplan->projectNotEmpty = '所属项目不能为空。';
$lang->productplan->nextStep = "下一步";
$lang->productplan->summary = "本页共
%s 个计划,父计划
%s ,子计划
%s ,独立计划
%s 。";
$lang->productplan->checkedSummary = "共选中
%total% 个计划,父计划
%parent% ,子计划
%child% ,独立计划
%independent% 。";
+$lang->productplan->confirmChangePlan = "分支『%s』解除关联后,分支下的%s个{$lang->SRCommon}和%s个Bug将同步从计划中移除,是否解除?";
+$lang->productplan->confirmRemoveStory = "分支『%s』解除关联后,分支下的%s个{$lang->SRCommon}将同步从计划中移除,是否解除?";
+$lang->productplan->confirmRemoveBug = "分支『%s』解除关联后,分支下的%s个Bug将同步从计划中移除,是否解除?";
$lang->productplan->id = '编号';
$lang->productplan->product = $lang->productCommon;
@@ -83,7 +86,7 @@ $lang->productplan->title = '名称';
$lang->productplan->desc = '描述';
$lang->productplan->begin = '开始日期';
$lang->productplan->end = '结束日期';
-$lang->productplan->status = '计划状态';
+$lang->productplan->status = '状态';
$lang->productplan->last = "上次计划";
$lang->productplan->future = '待定';
$lang->productplan->stories = "{$lang->SRCommon}数";
@@ -97,7 +100,6 @@ $lang->productplan->childrenAB = "子";
$lang->productplan->order = "排序";
$lang->productplan->deleted = "已删除";
$lang->productplan->mailto = "抄送给";
-$lang->productplan->status = "计划状态";
$lang->productplan->planStatus = "状态";
$lang->productplan->statusList['wait'] = '未开始';
@@ -136,6 +138,8 @@ $lang->productplan->beginGreaterChildTip = "父计划[%s]的开始日期:%s,
$lang->productplan->endLetterChildTip = "父计划[%s]的完成日期:%s,不能小于子计划的完成日期: %s";
$lang->productplan->beginLetterParentTip = "子计划[%s]的开始日期:%s,不能小于父计划的开始日期: %s";
$lang->productplan->endGreaterParentTip = "子计划[%s]的完成日期:%s,不能大于父计划的完成日期: %s";
+$lang->productplan->diffBranchesTip = "父计划的@branch@『%s』未被子计划关联,对应@branch@的需求和bug将自动从计划中移除,是否保存?";
+$lang->productplan->deleteBranchTip = "@branch@『%s』被子计划关联,无法修改。";
$lang->productplan->featureBar['browse']['all'] = '全部';
$lang->productplan->featureBar['browse']['undone'] = '未完成';
diff --git a/module/productplan/model.php b/module/productplan/model.php
index 0ff986ebe6..c063da4ec4 100644
--- a/module/productplan/model.php
+++ b/module/productplan/model.php
@@ -52,15 +52,28 @@ class productplanModel extends model
* @access public
* @return object
*/
- public function getLast($productID, $branch = 0, $parent = 0)
+ public function getLast($productID, $branch = '', $parent = 0)
{
+ $branchQuery = '';
+ if($branch !== '')
+ {
+ $branchQuery .= '(';
+ $branchCount = count(explode(',', $branch));
+ foreach(explode(',', $branch) as $index => $branchID)
+ {
+ $branchQuery .= "CONCAT(',', branch, ',') LIKE '%,$branchID,%'";
+ if($index < $branchCount - 1) $branchQuery .= ' AND ';
+ }
+ $branchQuery .= ')';
+ }
+
return $this->dao->select('*')->from(TABLE_PRODUCTPLAN)
->where('deleted')->eq(0)
->beginIF($parent <= 0)->andWhere('parent')->le((int)$parent)->fi()
->beginIF($parent > 0)->andWhere('parent')->eq((int)$parent)->fi()
->andWhere('product')->eq((int)$productID)
->andWhere('end')->ne($this->config->productplan->future)
- ->andWhere('branch')->eq($branch)
+ ->beginIF($branch !== '' and !empty($branchQuery))->andWhere($branchQuery)->fi()
->orderBy('end desc')
->limit(1)
->fetch();
@@ -209,16 +222,14 @@ class productplanModel extends model
* Get top plan pairs.
*
* @param int $productID
- * @param int $branch
* @param int $exclude
* @access public
* @return array
*/
- public function getTopPlanPairs($productID, $branch = '', $exclude = '')
+ public function getTopPlanPairs($productID, $exclude = '')
{
$planPairs = $this->dao->select("id,title")->from(TABLE_PRODUCTPLAN)
->where('product')->eq($productID)
- ->beginIF($branch !== '')->andWhere('branch')->eq($branch)->fi()
->andWhere('parent')->le(0)
->andWhere('deleted')->eq(0)
->beginIF($exclude)->andWhere('status')->notin($exclude)
@@ -240,28 +251,43 @@ class productplanModel extends model
*/
public function getPairs($product = 0, $branch = '', $param = '', $skipParent = false)
{
- $date = date('Y-m-d');
- $plans = $this->dao->select('t1.id,t1.title,t1.parent,t1.begin,t1.end,t2.name as branchName,t3.type as productType')->from(TABLE_PRODUCTPLAN)->alias('t1')
- ->leftJoin(TABLE_BRANCH)->alias('t2')->on('t2.id=t1.branch')
+ $this->app->loadLang('branch');
+
+ $date = date('Y-m-d');
+
+ $branchQuery = '';
+ if($branch !== '' and $branch != 'all')
+ {
+ $branchQuery .= '(';
+ $branchCount = count(explode(',', $branch));
+ foreach(explode(',', $branch) as $index => $branchID)
+ {
+ $branchQuery .= "FIND_IN_SET('$branchID', t1.branch)";
+ if($index < $branchCount - 1) $branchQuery .= ' OR ';
+ }
+ $branchQuery .= ')';
+ }
+
+ $plans = $this->dao->select('t1.id,t1.title,t1.parent,t1.begin,t1.end,t3.type as productType,t1.branch')->from(TABLE_PRODUCTPLAN)->alias('t1')
->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t3.id=t1.product')
->where('t1.product')->in($product)
->andWhere('t1.deleted')->eq(0)
- ->beginIF($branch !== '' and $branch != 'all')->andWhere('t1.branch')->in("0,$branch")->fi()
+ ->beginIF(!empty($branchQuery))->andWhere($branchQuery)->fi()
->beginIF(strpos($param, 'unexpired') !== false)->andWhere('t1.end')->ge($date)->fi()
->beginIF(strpos($param, 'noclosed') !== false)->andWhere('t1.status')->ne('closed')->fi()
->orderBy('t1.begin desc')
->fetchAll('id');
$plans = $this->reorder4Children($plans);
+ $plans = $this->relationBranch($plans);
$planPairs = array();
- $this->app->loadLang('branch');
foreach($plans as $plan)
{
if($skipParent and $plan->parent == '-1') continue;
if($plan->parent > 0 and isset($plans[$plan->parent])) $plan->title = $plans[$plan->parent]->title . ' /' . $plan->title;
$planPairs[$plan->id] = $plan->title . " [{$plan->begin} ~ {$plan->end}]";
if($plan->begin == $this->config->productplan->future and $plan->end == $this->config->productplan->future) $planPairs[$plan->id] = $plan->title . ' ' . $this->lang->productplan->future;
- if($plan->productType != 'normal') $planPairs[$plan->id] = ($plan->branchName ? $plan->branchName : $this->lang->branch->main) . ' / ' . $planPairs[$plan->id];
+ if($plan->productType != 'normal') $planPairs[$plan->id] = $planPairs[$plan->id] . ' / ' . ($plan->branchName ? $plan->branchName : $this->lang->branch->main);
}
return array('' => '') + $planPairs;
}
@@ -280,12 +306,26 @@ class productplanModel extends model
$date = date('Y-m-d');
$param = strtolower($param);
$branch = strpos($param, 'withmainplan') !== false ? "0,$branch" : $branch;
+
+ $branchQuery = '';
+ if($branch !== '' and $branch != 'all')
+ {
+ $branchQuery .= '(';
+ $branchCount = count(explode(',', $branch));
+ foreach(explode(',', $branch) as $index => $branchID)
+ {
+ $branchQuery .= "FIND_IN_SET('$branchID', branch)";
+ if($index < $branchCount - 1) $branchQuery .= ' OR ';
+ }
+ $branchQuery .= ')';
+ }
+
$plans = $this->dao->select('id,title,parent,begin,end')->from(TABLE_PRODUCTPLAN)
->where('product')->in($product)
->andWhere('deleted')->eq(0)
->beginIF(strpos($param, 'unexpired') !== false)->andWhere('end')->ge($date)->fi()
->beginIF(strpos($param, 'noclosed') !== false)->andWhere('status')->ne('closed')->fi()
- ->beginIF($branch !== 'all' and $branch !== '')->andWhere("branch")->in($branch)->fi()
+ ->beginIF($branch !== 'all' and $branch !== '' and !empty($branchQuery))->andWhere($branchQuery)->fi()
->orderBy('begin desc')
->fetchAll('id');
@@ -310,7 +350,7 @@ class productplanModel extends model
/**
* Get plans for products
*
- * @param int $products
+ * @param array $products
* @access public
* @return void
*/
@@ -348,32 +388,39 @@ class productplanModel extends model
{
$date = date('Y-m-d');
$param = strtolower($param);
- $plans = $this->dao->select('*')->from(TABLE_PRODUCTPLAN)
- ->where('deleted')->eq(0)
- ->beginIF($products)->andWhere('product')->in($products)->fi()
- ->beginIF(strpos($param, 'unexpired') !== false)->andWhere('end')->ge($date)->fi()
- ->orderBy($orderBy)
+ $plans = $this->dao->select('t1.*,t2.type as productType')->from(TABLE_PRODUCTPLAN)->alias('t1')
+ ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t2.id=t1.product')
+ ->where('t1.deleted')->eq(0)
+ ->beginIF($products)->andWhere('t1.product')->in($products)->fi()
+ ->beginIF(strpos($param, 'unexpired') !== false)->andWhere('t1.end')->ge($date)->fi()
+ ->orderBy('t1.' . $orderBy)
->fetchAll('id');
if(!empty($plans) and $field == 'name') $plans = $this->reorder4Children($plans);
+ $plans = $this->relationBranch($plans);
+
$parentTitle = array();
$planGroup = array();
foreach($plans as $plan)
{
- if(!isset($planGroup[$plan->product][$plan->branch])) $planGroup[$plan->product][$plan->branch] = array('' => '');
-
- if($plan->parent == '-1' and strpos($param, 'skipparent') !== false) continue;
-
- if($field == 'name')
+ foreach(explode(',', $plan->branch) as $branch)
{
- if($plan->parent > 0 and isset($plans[$plan->parent])) $plan->title = $plans[$plan->parent]->title . ' /' . $plan->title;
- $planGroup[$plan->product][$plan->branch][$plan->id] = $plan->title . " [{$plan->begin} ~ {$plan->end}]";
- if($plan->begin == $this->config->productplan->future and $plan->end == $this->config->productplan->future) $planGroup[$plan->product][$plan->branch][$plan->id] = $plan->title . ' ' . $this->lang->productplan->future;
- }
- else
- {
- $planGroup[$plan->product][$plan->branch][$plan->id] = $plan;
+ if(!isset($planGroup[$plan->product][$branch])) $planGroup[$plan->product][$branch] = array('' => '');
+
+ if($plan->parent == '-1' and strpos($param, 'skipparent') !== false) continue 2;
+
+ if($field == 'name')
+ {
+ if($plan->parent > 0 and isset($plans[$plan->parent])) $plan->title = $plans[$plan->parent]->title . ' /' . $plan->title;
+ $planGroup[$plan->product][$branch][$plan->id] = $plan->title . " [{$plan->begin} ~ {$plan->end}]";
+ if($plan->begin == $this->config->productplan->future and $plan->end == $this->config->productplan->future) $planGroup[$plan->product][$branch][$plan->id] = $plan->title . ' ' . $this->lang->productplan->future;
+ if($plan->productType != 'normal') $planGroup[$plan->product][$branch][$plan->id] = $planGroup[$plan->product][$branch][$plan->id] . ' / ' . ($plan->branchName ? $plan->branchName : $this->lang->branch->main);
+ }
+ else
+ {
+ $planGroup[$plan->product][$branch][$plan->id] = $plan;
+ }
}
}
return $planGroup;
@@ -413,26 +460,48 @@ class productplanModel extends model
*
* @param int $productID
* @param array $branches
+ * @param string $param
* @param bool $skipParent
* @access public
* @return array
*/
- public function getBranchPlanPairs($productID, $branches = '', $skipParent = false)
+ public function getBranchPlanPairs($productID, $branches = '', $param = '', $skipParent = false)
{
+ $branchQuery = '';
+ if($branches !== '' and $branches !== 'all')
+ {
+ $branchQuery .= '(';
+ if(!is_array($branches)) $branches = explode(',', $branches);
+ foreach($branches as $branchID)
+ {
+ $branchQuery .= "CONCAT(',', branch, ',') LIKE '%,$branchID,%'";
+ if($branchID != end($branches)) $branchQuery .= ' OR ';
+ }
+ $branchQuery .= ')';
+ }
+
+ $date = date('Y-m-d');
+ $param = strtolower($param);
$plans = $this->dao->select('parent,branch,id,title,begin,end')->from(TABLE_PRODUCTPLAN)
->where('product')->eq($productID)
->andWhere('deleted')->eq(0)
+ ->beginIF(!empty($branchQuery))->andWhere($branchQuery)->fi()
->beginIF($branches != '')->andWhere('branch')->in($branches)->fi()
+ ->beginIF(strpos($param, 'unexpired') !== false)->andWhere('end')->ge($date)->fi()
->orderBy('begin desc')
->fetchAll('id');
$planPairs = array();
foreach($plans as $planID => $plan)
{
- if($skipParent and $plan->parent == '-1') continue;
+ foreach(explode(',', $plan->branch) as $branch)
+ {
+ if($skipParent and $plan->parent == '-1') continue 2;
- if($plan->parent > 0 and isset($plans[$plan->parent])) $plan->title = $plans[$plan->parent]->title . ' /' . $plan->title;
- $planPairs[$plan->branch][$planID] = $plan->title . ' [' . $plan->begin . '~' . $plan->end . ']';
+ if($plan->parent > 0 and isset($plans[$plan->parent])) $plan->title = $plans[$plan->parent]->title . ' /' . $plan->title;
+ $planPairs[$branch][$planID] = $plan->title . ' [' . $plan->begin . '~' . $plan->end . ']';
+ if($branch !== BRANCH_MAIN) $planPairs[BRANCH_MAIN][$planID] = $plan->title . ' [' . $plan->begin . '~' . $plan->end . ']';
+ }
}
return $planPairs;
}
@@ -450,9 +519,18 @@ class productplanModel extends model
->setIF($this->post->future || empty($_POST['end']), 'end', $this->config->productplan->future)
->setDefault('createdBy', $this->app->user->account)
->setDefault('createdDate', helper::now())
+ ->setDefault('branch', 0)
+ ->join('branch', ',')
->remove('delta,uid,future')
->get();
+ $product = $this->loadModel('product')->getByID($plan->product);
+ if($product->type != 'normal' and !isset($_POST['branch']))
+ {
+ $this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
+ dao::$errors['branch'] = sprintf($this->lang->error->notempty, $this->lang->product->branch);
+ }
+
if($plan->parent > 0)
{
$parentPlan = $this->getByID($plan->parent);
@@ -477,8 +555,7 @@ class productplanModel extends model
if(dao::isError()) return false;
$plan = $this->loadModel('file')->processImgURL($plan, $this->config->productplan->editor->create['id'], $this->post->uid);
- $this->dao->insert(TABLE_PRODUCTPLAN)
- ->data($plan)
+ $this->dao->insert(TABLE_PRODUCTPLAN)->data($plan)
->autoCheck()
->batchCheck($this->config->productplan->create->requiredFields, 'notempty')
->checkIF(!$this->post->future && !empty($_POST['begin']) && !empty($_POST['end']), 'end', 'ge', $plan->begin)
@@ -489,24 +566,10 @@ class productplanModel extends model
$planID = $this->dao->lastInsertID();
$this->file->updateObjectID($this->post->uid, $planID, 'plan');
$this->loadModel('score')->create('productplan', 'create', $planID);
- if(!empty($plan->parent))
+ if(!empty($plan->parent) and $parentPlan->parent == '0')
{
- if($parentPlan->parent == '0')
- {
- $this->dao->update(TABLE_PRODUCTPLAN)->set('parent')->eq('-1')->where('id')->eq($plan->parent)->andWhere('parent')->eq('0')->exec();
-
- /* Transfer stories and bugs linked with the parent plan to the child plan. */
- $this->dao->update(TABLE_PLANSTORY)->set('plan')->eq($planID)->where('plan')->eq($plan->parent)->exec();
- $this->dao->update(TABLE_BUG)->set('plan')->eq($planID)->where('plan')->eq($plan->parent)->exec();
- $stories = $this->dao->select('*')->from(TABLE_STORY)->where("CONCAT(',', plan, ',')")->like("%,{$plan->parent},%")->fetchAll('id');
- foreach($stories as $storyID => $story)
- {
- $storyPlan = str_replace(",{$plan->parent},", ",$planID,", ",$story->plan,");
- $storyPlan = trim($storyPlan, ',');
-
- $this->dao->update(TABLE_STORY)->set('plan')->eq($storyPlan)->where('id')->eq($storyID)->exec();
- }
- }
+ $plan->id = $planID;
+ $this->transferStoriesAndBugs($plan);
}
return $planID;
}
@@ -525,10 +588,51 @@ class productplanModel extends model
$plan = fixer::input('post')->stripTags($this->config->productplan->editor->edit['id'], $this->config->allowedTags)
->setIF($this->post->future or empty($_POST['begin']), 'begin', $this->config->productplan->future)
->setIF($this->post->future or empty($_POST['end']), 'end', $this->config->productplan->future)
+ ->setDefault('branch', 0)
+ ->join('branch', ',')
->add('id', $planID)
->remove('delta,uid,future')
->get();
+ $product = $this->loadModel('product')->getByID($oldPlan->product);
+ if($product->type != 'normal')
+ {
+ if(!isset($_POST['branch']))
+ {
+ $this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
+ dao::$errors['branch'] = sprintf($this->lang->error->notempty, $this->lang->product->branch);
+ }
+ else
+ {
+ if($oldPlan->parent == -1)
+ {
+ /* Get branches of child plans. */
+ $childBranches = array();
+ $childPlans = $this->getChildren($oldPlan->id);
+ $branchPairs = $this->loadModel('branch')->getPairs($oldPlan->product);
+ foreach($childPlans as $children)
+ {
+ foreach(explode(',', $children->branch) as $childBranchID) $childBranches[$childBranchID] = $childBranchID;
+ }
+
+ /* Get branches of parent plan that cannot delete. */
+ $canDeleteBranch = true;
+ $deleteBranches = '';
+ foreach(explode(',', $oldPlan->branch) as $oldBranchID)
+ {
+ if(strpos(",$plan->branch,", ",$oldBranchID,") === false and isset($childBranches[$oldBranchID]))
+ {
+ $canDeleteBranch = false;
+ if(isset($branchPairs[$oldBranchID])) $deleteBranches .= "{$branchPairs[$oldBranchID]},";
+ }
+ }
+
+ $this->lang->productplan->deleteBranchTip = str_replace('@branch@', $this->lang->product->branchName[$product->type], $this->lang->productplan->deleteBranchTip);
+ if(!$canDeleteBranch) dao::$errors[] = sprintf($this->lang->productplan->deleteBranchTip, trim($deleteBranches, ','));
+ }
+ }
+ }
+
$parentPlan = $this->getByID($plan->parent);
$futureTime = $this->config->productplan->future;
@@ -545,9 +649,9 @@ class productplanModel extends model
}
elseif($oldPlan->parent == -1 and ($plan->begin != $futureTime or $plan->end != $futureTime))
{
- $childPlans = $this->getChildren($planID);
- $minBegin = $plan->begin;
- $maxEnd = $plan->end;
+ $childPlans = $this->getChildren($planID);
+ $minBegin = $plan->begin;
+ $maxEnd = $plan->end;
foreach($childPlans as $childID => $childPlan)
{
$childPlan = isset($plans[$childID]) ? $plans[$childID] : $childPlan;
@@ -560,8 +664,7 @@ class productplanModel extends model
if(dao::isError()) return false;
$plan = $this->loadModel('file')->processImgURL($plan, $this->config->productplan->editor->edit['id'], $this->post->uid);
- $this->dao->update(TABLE_PRODUCTPLAN)
- ->data($plan)
+ $this->dao->update(TABLE_PRODUCTPLAN)->data($plan)
->autoCheck()
->batchCheck($this->config->productplan->edit->requiredFields, 'notempty')
->checkIF(!$this->post->future && !empty($_POST['begin']) && !empty($_POST['end']), 'end', 'ge', $plan->begin)
@@ -576,25 +679,7 @@ class productplanModel extends model
if(!dao::isError())
{
$this->file->updateObjectID($this->post->uid, $planID, 'plan');
- if(!empty($plan->parent))
- {
- if($parentPlan->parent == '0')
- {
- $this->dao->update(TABLE_PRODUCTPLAN)->set('parent')->eq('-1')->where('id')->eq($plan->parent)->andWhere('parent')->eq('0')->exec();
-
- /* Transfer stories and bugs linked with the parent plan to the child plan. */
- $this->dao->update(TABLE_PLANSTORY)->set('plan')->eq($planID)->where('plan')->eq($plan->parent)->exec();
- $this->dao->update(TABLE_BUG)->set('plan')->eq($planID)->where('plan')->eq($plan->parent)->exec();
- $stories = $this->dao->select('*')->from(TABLE_STORY)->where("CONCAT(',', plan, ',')")->like("%,{$plan->parent},%")->fetchAll('id');
- foreach($stories as $storyID => $story)
- {
- $storyPlan = str_replace(",{$plan->parent},", ",$planID,", ",$story->plan,");
- $storyPlan = trim($storyPlan, ',');
-
- $this->dao->update(TABLE_STORY)->set('plan')->eq($storyPlan)->where('id')->eq($storyID)->exec();
- }
- }
- }
+ if(!empty($plan->parent) and $parentPlan->parent == '0') $this->transferStoriesAndBugs($plan);
return common::createChanges($oldPlan, $plan);
}
}
@@ -707,13 +792,20 @@ class productplanModel extends model
$plans = array();
$extendFields = $this->getFlowExtendFields();
+ $product = $this->loadModel('product')->getByID($productID);
foreach($data->id as $planID)
{
+ if($product->type != 'normal' and $oldPlans[$planID]->parent != '-1' and !isset($data->branch[$planID]))
+ {
+ $this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
+ return helper::end(js::error(sprintf($this->lang->error->notempty, $this->lang->product->branch)));
+ }
+
$isFuture = isset($data->future[$planID]) ? true : false;
if(isset($data->status[$planID]) and $data->status[$planID] != 'wait') $isFuture = false;
$plan = new stdclass();
- $plan->branch = isset($data->branch[$planID]) ? $data->branch[$planID] : $oldPlans[$planID]->branch;
+ $plan->branch = isset($data->branch[$planID]) ? join(',', $data->branch[$planID]) : $oldPlans[$planID]->branch;
$plan->title = $data->title[$planID];
$plan->begin = isset($data->begin[$planID]) ? $data->begin[$planID] : '';
$plan->end = isset($data->end[$planID]) ? $data->end[$planID] : '';
@@ -721,8 +813,8 @@ class productplanModel extends model
$plan->parent = $oldPlans[$planID]->parent;
$plan->id = $planID;
- if(empty($plan->title)) return print(js::alert(sprintf($this->lang->productplan->errorNoTitle, $planID)));
- if($plan->begin > $plan->end and !empty($plan->end)) return print(js::alert(sprintf($this->lang->productplan->beginGeEnd, $planID)));
+ if(empty($plan->title)) return helper::end(js::alert(sprintf($this->lang->productplan->errorNoTitle, $planID)));
+ if($plan->begin > $plan->end and !empty($plan->end)) return helper::end(js::alert(sprintf($this->lang->productplan->beginGeEnd, $planID)));
if($plan->begin == '') $plan->begin = $this->config->productplan->future;
if($plan->end == '') $plan->end = $this->config->productplan->future;
@@ -746,14 +838,14 @@ class productplanModel extends model
/* Determine whether the begin and end dates of the parent plan and the child plan are correct. */
if($parentID > 0)
{
- $parent = isset($plans[$parentID]) ? $plans[$parentID] : $this->getByID($parentID);
+ $parent = isset($plans[$parentID]) ? $plans[$parentID] : $this->getByID($parentID);
if($parent->begin != $this->config->productplan->future and $plan->begin != $this->config->productplan->future and $plan->begin < $parent->begin)
{
- return print(js::alert(sprintf($this->lang->productplan->beginLetterParentTip, $planID, $plan->begin, $parent->begin)));
+ return helper::end(js::alert(sprintf($this->lang->productplan->beginLetterParentTip, $planID, $plan->begin, $parent->begin)));
}
elseif($parent->end != $this->config->productplan->future and $plan->end != $this->config->productplan->future and $plan->end > $parent->end)
{
- return print(js::alert(sprintf($this->lang->productplan->endGreaterParentTip, $planID, $plan->end, $parent->end)));
+ return helper::end(js::alert(sprintf($this->lang->productplan->endGreaterParentTip, $planID, $plan->end, $parent->end)));
}
}
elseif($parentID == -1 and $plan->begin != $this->config->productplan->future)
@@ -767,8 +859,8 @@ class productplanModel extends model
if($childPlan->begin < $minBegin and $minBegin != $this->config->productplan->future) $minBegin = $childPlan->begin;
if($childPlan->end > $maxEnd and $maxEnd != $this->config->productplan->future) $maxEnd = $childPlan->end;
}
- if($minBegin < $plan->begin and $minBegin != $this->config->productplan->future) return print(js::alert(sprintf($this->lang->productplan->beginGreaterChildTip, $planID, $plan->begin, $minBegin)));
- if($maxEnd > $plan->end and $maxEnd != $this->config->productplan->future) return print(js::alert(sprintf($this->lang->productplan->endLetterChildTip, $planID, $plan->end, $maxEnd)));
+ if($minBegin < $plan->begin and $minBegin != $this->config->productplan->future) return helper::end(js::alert(sprintf($this->lang->productplan->beginGreaterChildTip, $planID, $plan->begin, $minBegin)));
+ if($maxEnd > $plan->end and $maxEnd != $this->config->productplan->future) return helper::end(js::alert(sprintf($this->lang->productplan->endLetterChildTip, $planID, $plan->end, $maxEnd)));
}
$change = common::createChanges($oldPlans[$planID], $plan);
@@ -776,7 +868,7 @@ class productplanModel extends model
{
if($parentID > 0 and !isset($parents[$parentID])) $parents[$parentID] = $parentID;
$this->dao->update(TABLE_PRODUCTPLAN)->data($plan)->autoCheck()->checkFlow()->where('id')->eq($planID)->exec();
- if(dao::isError()) return print(js::error(dao::getError()));
+ if(dao::isError()) return helper::end(js::error(dao::getError()));
$changes[$planID] = $change;
}
}
@@ -918,27 +1010,9 @@ class productplanModel extends model
if(strpos(",$story->plan,", ",{$planID},") !== false) continue;
/* Update the plan linked with the story and the order of the story in the plan. */
- if($this->session->currentProductType == 'normal' or $story->branch != 0 or empty($story->plan))
- {
- $this->dao->update(TABLE_STORY)->set("plan")->eq($planID)->where('id')->eq((int)$storyID)->exec();
+ $this->dao->update(TABLE_STORY)->set("plan")->eq($planID)->where('id')->eq((int)$storyID)->exec();
- $this->story->updateStoryOrderOfPlan($storyID, $planID, $story->plan);
- }
- else
- {
- $branchPlanPairs = $this->dao->select('branch, id as plan')->from(TABLE_PRODUCTPLAN)
- ->where('product')->eq($story->product)
- ->andWhere('id')->in($story->plan)
- ->fetchPairs();
- if(isset($branchPlanPairs[$plan->branch])) $branchPlanPairs[$plan->branch] = $planID;
-
- $plansOfStory = implode(',', $branchPlanPairs);
- $plansOfStory = trim($plansOfStory, ',');
-
- $this->dao->update(TABLE_STORY)->set("plan")->eq($plansOfStory)->where('id')->eq((int)$storyID)->exec();
-
- $this->story->updateStoryOrderOfPlan($storyID, $planID);
- }
+ $this->story->updateStoryOrderOfPlan($storyID, $planID, $story->plan);
$this->action->create('story', $storyID, 'linked2plan', '', $planID);
$this->story->setStage($storyID);
@@ -1026,9 +1100,12 @@ class productplanModel extends model
$planStory = $this->loadModel('story')->getPlanStories($planID);
if(!empty($planStory))
{
+ $projectProducts = $this->loadModel('project')->getBranchesByProject($projectID);
+
foreach($planStory as $id => $story)
{
- if($story->status == 'draft' or $story->status == 'reviewing')
+ $projectBranches = zget($projectProducts, $story->product, array());
+ if($story->status == 'active' or (!empty($story->branch) and !empty($projectBranches) and !isset($projectBranches[$story->branch])))
{
unset($planStory[$id]);
continue;
@@ -1080,6 +1157,46 @@ class productplanModel extends model
return $plans;
}
+ /**
+ * Get relation branch for plans.
+ *
+ * @param array $plans
+ * @access public
+ * @return array
+ */
+ public function relationBranch($plans)
+ {
+ if(empty($plans)) return $plans;
+
+ $this->app->loadLang('branch');
+ $branchMap = $this->dao->select('id, name')->from(TABLE_BRANCH)
+ ->where('status')->eq('active')
+ ->andWhere('deleted')->eq('0')
+ ->fetchPairs('id', 'name');
+ $branchMap[BRANCH_MAIN] = $this->lang->branch->main;
+
+ foreach($plans as &$plan)
+ {
+ if($plan->branch)
+ {
+ $branchName = '';
+ foreach(explode(',', $plan->branch) as $planBranch)
+ {
+ $branchName .= isset($branchMap[$planBranch]) ? $branchMap[$planBranch] : '';
+ $branchName .= ',';
+ }
+
+ $plan->branchName = trim($branchName, ',');
+ }
+ else
+ {
+ $plan->branchName = $this->lang->branch->main;
+ }
+ }
+
+ return $plans;
+ }
+
/**
* Judge an action is clickable or not.
*
@@ -1246,4 +1363,67 @@ class productplanModel extends model
if($product->type == 'normal') unset($this->config->productplan->search['fields']['branch']);
$this->loadModel('search')->setSearchParams($this->config->productplan->search);
}
+
+ /**
+ * Transfer stories and bugs to new plan.
+ *
+ * @param object $plan
+ * @access public
+ * @return void
+ */
+ public function transferStoriesAndBugs($plan)
+ {
+ $this->dao->update(TABLE_PRODUCTPLAN)->set('parent')->eq('-1')->where('id')->eq($plan->parent)->andWhere('parent')->eq('0')->exec();
+
+ /* Transfer stories linked with the parent plan to the child plan. */
+ $stories = $this->dao->select('*')->from(TABLE_STORY)->where("CONCAT(',', plan, ',')")->like("%,{$plan->parent},%")->fetchAll('id');
+ $unlinkStories = array();
+ foreach($stories as $storyID => $story)
+ {
+ if(!empty($story->branch) and strpos(",$plan->branch,", ",$story->branch,") === false)
+ {
+ $unlinkStories[$storyID] = $storyID;
+ $storyPlan = str_replace(",{$plan->parent},", ',', ",$story->plan,");
+ }
+ else
+ {
+ $storyPlan = str_replace(",{$plan->parent},", ",$plan->id,", ",$story->plan,");
+ }
+ $storyPlan = trim($storyPlan, ',');
+
+ $this->dao->update(TABLE_STORY)->set('plan')->eq($storyPlan)->where('id')->eq($storyID)->exec();
+ }
+ if(!empty($unlinkStories)) $this->dao->delete()->from(TABLE_PLANSTORY)->where('plan')->eq($plan->parent)->andWhere('story')->in($unlinkStories)->exec();
+ $this->dao->update(TABLE_PLANSTORY)->set('plan')->eq($plan->id)->where('plan')->eq($plan->parent)->exec();
+
+ /* Transfer bugs linked with the parent plan to the child plan. */
+ $bugs = $this->dao->select('*')->from(TABLE_BUG)->where('plan')->eq($plan->parent)->fetchAll('id');
+ $unlinkBugs = array();
+ foreach($bugs as $bugID => $bug)
+ {
+ if(!empty($bug->branch) and strpos(",$plan->branch,", ",$bug->branch,") === false) $unlinkBugs[$bugID] = $bugID;
+ }
+ if(!empty($unlinkBugs)) $this->dao->update(TABLE_BUG)->set('plan')->eq(0)->where('plan')->eq($plan->parent)->andWhere('id')->in($unlinkBugs)->exec();
+ $this->dao->update(TABLE_BUG)->set('plan')->eq($plan->id)->where('plan')->eq($plan->parent)->exec();
+ }
+
+ /**
+ * Get plan list by ids.
+ *
+ * @param array $planIds
+ * @param boo $order
+ * @access public
+ * @return array
+ */
+ public function getListByIds($planIds, $order = false)
+ {
+ $plans = $this->dao->select('*')->from(TABLE_PRODUCTPLAN)
+ ->where('id')->in($planIds)
+ ->orderBy('begin desc')
+ ->fetchAll('id');
+
+ if($order) $plans = $this->relationBranch($plans);
+
+ return $plans;
+ }
}
diff --git a/module/productplan/view/batchedit.html.php b/module/productplan/view/batchedit.html.php
index 545a57289a..5ce19034f2 100644
--- a/module/productplan/view/batchedit.html.php
+++ b/module/productplan/view/batchedit.html.php
@@ -23,7 +23,7 @@
productplan->id?>
type != 'normal'):?>
- productplan->branch;?>
+ product->branch;?>
productplan->title?>
productplan->status?>
@@ -43,8 +43,18 @@
id . html::hidden("id[$plan->id]", $plan->id);?>
type != 'normal'):?>
- parent == -1 ? "disabled='disabled'" : '';?>
- id]", $plan->parent == '-1' ? '' : $branchTagOption, $plan->branch, "onchange='getConflictStories($plan->id, this.value); 'class='form-control chosen' $disabled");?>
+ parent == -1 ? "disabled='disabled'" : '';
+ $parentBranches = array();
+ if($plan->parent > 0 and isset($parentList[$plan->parent]))
+ {
+ foreach($branchTagOption as $branchID => $branchName)
+ {
+ if(strpos(",{$parentList[$plan->parent]->branch},", ",$branchID,") !== false) $parentBranches[$branchID] = $branchName;
+ }
+ }
+ echo html::select("branch[$plan->id][]", ($plan->parent > 0 and !empty($parentBranches)) ? $parentBranches : $branchTagOption, $plan->branch, "onchange='getConflictStories($plan->id); 'class='form-control chosen' multiple $disabled");
+ ?>
id]", $plan->title, "class='form-control'")?>
diff --git a/module/productplan/view/browsebylist.html.php b/module/productplan/view/browsebylist.html.php
index a81c574f0e..c46b8cd66c 100644
--- a/module/productplan/view/browsebylist.html.php
+++ b/module/productplan/view/browsebylist.html.php
@@ -84,7 +84,7 @@
productplan->status);?>
session->currentProductType != 'normal'):?>
- productplan->branch);?>
+ product->branchName[$product->type]);?>
productplan->begin);?>
productplan->end);?>
@@ -161,7 +161,9 @@
status)?>
session->currentProductType != 'normal'):?>
- parent != '-1') echo $branchOption[$plan->branch];?>
+
+ branch) as $branchID) $planBranches .= $branchOption[$branchID] . ',';?>
+ '>
begin == $config->productplan->future ? $lang->productplan->future : $plan->begin;?>
end == $config->productplan->future ? $lang->productplan->future : $plan->end;?>
diff --git a/module/productplan/view/create.html.php b/module/productplan/view/create.html.php
index 823eb1a0e4..4e298be29d 100644
--- a/module/productplan/view/create.html.php
+++ b/module/productplan/view/create.html.php
@@ -15,6 +15,7 @@
execution->weekend);?>
productplan->last);?>
+
@@ -35,24 +36,33 @@
productplan->product;?>
name;?>
+
+
+
+ productplan->parent;?>
+ '') + $parentPlanPairs, 0, "class='form-control chosen'");?>
+
+
type != 'normal'):?>
product->branch;?>
-
+ $branchName)
+ {
+ if(strpos(",$parentPlan->branch,", ",$branchID,") === false) unset($branches[$branchID]);
+ }
+ }
+ ?>
+
-
productplan->title;?>
productplan->last . ': ' . $lastPlan->title . ')';?>
-
-
- productplan->parent;?>
- '') + $parentPlanPairs, '', "class='form-control chosen'");?>
-
-
productplan->begin;?>
@@ -79,7 +89,6 @@
id);?>
- branch);?>
diff --git a/module/productplan/view/edit.html.php b/module/productplan/view/edit.html.php
index 2deed208bc..0f783196c1 100644
--- a/module/productplan/view/edit.html.php
+++ b/module/productplan/view/edit.html.php
@@ -17,6 +17,7 @@
+id);?>
@@ -30,25 +31,25 @@
productplan->product;?>
name;?>
- type != 'normal' and $plan->parent != '-1'):?>
-
- product->branch;?>
- branch, "onchange='getConflictStories($plan->id, this.value); 'class='form-control'");?>
-
-
-
- productplan->title;?>
- title, "class='form-control' required");?>
-
parent == '-1'):?>
parent);?>
productplan->parent;?>
- '') + $parentPlanPairs, $plan->parent, "class='form-control chosen'");?>
+ '') + $parentPlanPairs, $plan->parent, "class='form-control chosen'");?>
+ shadow and $product->type != 'normal'):?>
+
+ product->branch;?>
+ branch, "class='form-control chosen' multiple");?>
+
+
+
+ productplan->title;?>
+ title, "class='form-control' required");?>
+
productplan->status;?>
parent == -1 ? "disabled='disabled'" : '' ;?>
diff --git a/module/productplan/view/view.html.php b/module/productplan/view/view.html.php
index 4a7c514fda..d01f599e3b 100644
--- a/module/productplan/view/view.html.php
+++ b/module/productplan/view/view.html.php
@@ -31,7 +31,6 @@
id;?>
title;?>
- type !== 'normal') echo "" . $branchOption[$branch] . ' ';?>
begin == $config->productplan->future || $plan->end == $config->productplan->future) ? $lang->productplan->future : $plan->begin . '~' . $plan->end;?>
@@ -125,8 +124,8 @@
idAB);?>
- priAB);?>
story->title);?>
+ priAB);?>
statusAB);?>
@@ -140,15 +139,15 @@
productplan->updateOrder);?>
- priAB);?>
- story->module);?>
- story->title);?>
- openedByAB);?>
- assignedToAB);?>
- story->estimateAB);?>
- statusAB);?>
- story->stageAB);?>
- actions?>
+ story->title);?>
+ story->module);?>
+ priAB);?>
+ statusAB);?>
+ openedByAB);?>
+ assignedToAB);?>
+ story->estimateAB);?>
+ story->stageAB);?>
+ actions?>
@@ -166,13 +165,13 @@
id);?>
-
" title='story->priList, $story->pri);?>'>story->priList, $story->pri);?>
parent > 0) echo "story->children}>{$lang->story->childrenAB} ";
echo $story->title;
?>
+
" title='story->priList, $story->pri);?>'>story->priList, $story->pri);?>
processStatus('story', $story);?>
@@ -187,22 +186,20 @@
-
" title='story->priList, $story->pri);?>'>story->priList, $story->pri);?>
-
'>module, '');?>
parent > 0) echo "story->children}>{$lang->story->childrenAB} ";
echo html::a($viewLink , $story->title, '', "style='color: $story->color' data-app={$this->app->tab}");
?>
+
'>module, '');?>
+
" title='story->priList, $story->pri);?>'>story->priList, $story->pri);?>
+
+ processStatus('story', $story);?>
+
openedBy);?>
assignedTo);?>
estimate . $config->hourUnit;?>
-
-
- processStatus('story', $story);?>
-
-
story->stageList[$story->stage];?>
idAB);?>
- priAB);?>
bug->title);?>
+
priAB);?>
bug->status);?>
@@ -432,12 +429,12 @@
idAB);?>
-
priAB);?>
-
bug->title);?>
+
bug->title);?>
+
priAB);?>
+
bug->status);?>
openedByAB);?>
bug->assignedToAB);?>
-
bug->status);?>
-
actions?>
+
actions?>
@@ -448,12 +445,10 @@
id);?>
-
bug->priList, $bug->pri);?>
title?>
+
bug->priList, $bug->pri);?>
-
- processStatus('bug', $bug);?>
-
+ processStatus('bug', $bug);?>
@@ -463,15 +458,13 @@
id);?>
-
bug->priList, $bug->pri);?>
createLink('bug', 'view', "bugID=$bug->id"), $bug->title, '', "data-app={$this->app->tab}");?>
+
bug->priList, $bug->pri);?>
+
+ processStatus('bug', $bug);?>
+
openedBy);?>
assignedTo);?>
-
-
- processStatus('bug', $bug);?>
-
-
type != 'normal'):?>
product->branch;?>
- branch];?>
+
+
diff --git a/module/program/control.php b/module/program/control.php
index 57555ca63f..85b733eaf0 100644
--- a/module/program/control.php
+++ b/module/program/control.php
@@ -29,8 +29,8 @@ class program extends control
/**
* Program list.
*
- * @param varchar $status
- * @param varchar $orderBy
+ * @param string $status
+ * @param string $orderBy
* @access public
* @return void
*/
diff --git a/module/programplan/model.php b/module/programplan/model.php
index c68c38571f..2428208663 100755
--- a/module/programplan/model.php
+++ b/module/programplan/model.php
@@ -243,11 +243,11 @@ class programplanModel extends model
$oldTasks = $oldData->task;
foreach($oldTasks as $id => $oldTask)
{
- if(!isset($tasks->$id)) continue;
- $tasks->$id->version = $oldTask->version;
- $tasks->$id->name = $oldTask->name;
- $tasks->$id->estStarted = $oldTask->estStarted;
- $tasks->$id->deadline = $oldTask->deadline;
+ if(!isset($tasks[$id])) continue;
+ $tasks[$id]->version = $oldTask->version;
+ $tasks[$id]->name = $oldTask->name;
+ $tasks[$id]->estStarted = $oldTask->estStarted;
+ $tasks[$id]->deadline = $oldTask->deadline;
}
}
diff --git a/module/programplan/view/gantt.html.php b/module/programplan/view/gantt.html.php
index 99ee3bb853..57963e1880 100755
--- a/module/programplan/view/gantt.html.php
+++ b/module/programplan/view/gantt.html.php
@@ -240,9 +240,8 @@ function drawGanttToCanvas(exportType, successCallback, errorCallback)
var $ganttDataArea = $ganttView.find('.gantt_data_area');
var $ganttDridData = $ganttView.find('.gantt_grid_data');
- var ganttHeight = $ganttView.find('.gantt_grid_scale').outerHeight();
- ganttHeight += * 25;
-
+ var ganttRowHeight = $ganttView.find('.gantt_row').first().outerHeight() || 25;
+ var ganttHeight = $ganttView.find('.gantt_grid_scale').outerHeight() + * ganttRowHeight;
var ganttWidth = $ganttDridData.width() - 100;
diff --git a/module/project/control.php b/module/project/control.php
index 1886938275..c90cc33d91 100755
--- a/module/project/control.php
+++ b/module/project/control.php
@@ -492,12 +492,9 @@ class project extends control
$planIdList = array();
foreach($_POST['plans'] as $plans)
{
- foreach($plans as $planList)
+ foreach($plans as $planID)
{
- foreach($planList as $planID)
- {
- $planIdList[$planID] = $planID;
- }
+ $planIdList[$planID] = $planID;
}
}
@@ -587,10 +584,8 @@ class project extends control
if(!$copyProject->hasProduct) $shadow = 1;
foreach($products as $product)
{
- foreach($product->branches as $branch)
- {
- $productPlans[$product->id][$branch] = $this->loadModel('productplan')->getPairs($product->id, $branch, 'noclosed', true);
- }
+ $branches = implode(',', $product->branches);
+ $productPlans[$product->id] = $this->loadModel('productplan')->getPairs($product->id, $branches, 'noclosed', true);
}
}
@@ -722,9 +717,8 @@ class project extends control
$withProgram = $this->config->systemMode == 'ALM' ? true : false;
- $linkedBranches = array();
$linkedBranchList = array();
- $productPlans = array(0 => '');
+ $productPlans = array();
$branches = $this->project->getBranchesByProject($projectID);
$linkedProductIdList = empty($branches) ? '' : array_keys($branches);
$allProducts = $this->program->getProductPairs($project->parent, 'all', 'noclosed', '', 0, $withProgram);
@@ -743,11 +737,10 @@ class project extends control
if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->name;
foreach($branches[$productID] as $branchID => $branch)
{
- $linkedBranchList[$branchID] = $branchID;
- $linkedBranches[$productID][$branchID] = $branchID;
+ $linkedBranchList[$branchID] = $branchID;
- if($branch != BRANCH_MAIN) $productPlans[$productID][$branchID] = isset($plans[$productID][BRANCH_MAIN]) ? $plans[$productID][BRANCH_MAIN] : array();
- $productPlans[$productID][$branchID] += isset($plans[$productID][$branchID]) ? $plans[$productID][$branchID] : array();
+ if(!isset($productPlans[$productID])) $productPlans[$productID] = isset($plans[$productID][BRANCH_MAIN]) ? $plans[$productID][BRANCH_MAIN] : array();
+ $productPlans[$productID] += isset($plans[$productID][$branchID]) ? $plans[$productID][$branchID] : array();
if(!empty($projectStories[$productID][$branchID]) or !empty($projectBranches[$productID][$branchID]))
{
@@ -758,6 +751,17 @@ class project extends control
}
}
+ $productPlansOrder = array();
+ foreach($productPlans as $productID => $plan)
+ {
+ $orderPlans = $this->loadModel('productPlan')->getListByIds(array_keys($plan), true);
+ $orderPlansMap = array_keys($orderPlans);
+ foreach($orderPlansMap as $planMapID)
+ {
+ $productPlansOrder[$productID][$planMapID] = $productPlans[$productID][$planMapID];
+ }
+ }
+
if($project->model != 'kanban') $canChangeModel = $this->project->checkCanChangeModel($projectID, $project->model);
$this->view->title = $this->lang->project->edit;
@@ -771,9 +775,8 @@ class project extends control
$this->view->projectID = $projectID;
$this->view->allProducts = array('0' => '') + $allProducts;
$this->view->multiBranchProducts = $this->loadModel('product')->getMultiBranchPairs();
- $this->view->productPlans = array_filter($productPlans);
+ $this->view->productPlans = array_filter($productPlansOrder);
$this->view->linkedProducts = $linkedProducts;
- $this->view->linkedBranches = $linkedBranches;
$this->view->branches = $branches;
$this->view->executions = $this->execution->getPairs($projectID);
$this->view->unmodifiableProducts = $unmodifiableProducts;
@@ -1456,24 +1459,33 @@ class project extends control
/* Set project builds. */
$projectBuilds = array();
$productList = $this->product->getProducts($projectID);
- $this->app->loadLang('branch');
+ $showBranch = false;
if(!empty($builds))
{
foreach($builds as $build)
{
$build->builds = $this->build->getByList($build->builds);
- /* If product is normal, unset branch name. */
- if(isset($productList[$build->product]) and $productList[$build->product]->type == 'normal')
- {
- $build->branchName = '';
- }
- else
- {
- $build->branchName = isset($build->branchName) ? $build->branchName : $this->lang->branch->main;
- }
-
$projectBuilds[$build->product][] = $build;
}
+
+ /* Get branch name. */
+ $branchGroups = $this->loadModel('branch')->getByProducts(array_keys($projectBuilds));
+ foreach($builds as $build)
+ {
+ $build->branchName = '';
+ if(isset($branchGroups[$build->product]))
+ {
+ $showBranch = true;
+ $branchPairs = $branchGroups[$build->product];
+ foreach(explode(',', trim($build->branch, ',')) as $branchID)
+ {
+ if(isset($branchPairs[$branchID])) $build->branchName .= "{$branchPairs[$branchID]},";
+ }
+ $build->branchName = trim($build->branchName, ',');
+
+ if(empty($build->branchName) and empty($build->builds)) $build->branchName = $this->lang->branch->main;
+ }
+ }
}
/* Header and position. */
@@ -1490,6 +1502,7 @@ class project extends control
$this->view->executions = $this->execution->getPairs();
$this->view->buildPairs = $this->loadModel('build')->getBuildPairs(0);
$this->view->type = $type;
+ $this->view->showBranch = $showBranch;
$this->display();
}
@@ -1618,11 +1631,12 @@ class project extends control
$this->project->setMenu($projectID);
$project = $this->project->getById($projectID);
+ $deptID = $this->app->user->admin ? 0 : $this->app->user->dept;
$this->view->title = $project->name . $this->lang->colon . $this->lang->project->team;
$this->view->projectID = $projectID;
$this->view->teamMembers = $this->project->getTeamMembers($projectID);
- $this->view->deptUsers = $this->loadModel('dept')->getDeptUserPairs($this->app->user->dept, 'id');
+ $this->view->deptUsers = $this->loadModel('dept')->getDeptUserPairs($deptID, 'id');
$this->view->canBeChanged = common::canModify('project', $project);
$this->display();
diff --git a/module/project/css/create.css b/module/project/css/create.css
index cddcaaa13a..9d2c6d922c 100755
--- a/module/project/css/create.css
+++ b/module/project/css/create.css
@@ -15,16 +15,14 @@
#budget {border-right: 0px;}
#budgetUnit {border-left: 0px;}
#projectName {display: inline-block; width: 32%;}
-#productsBox a {border-radius: 2px !important;}
-#productsBox .col-sm-4 {padding-right: 5px;}
-#productsBox .required:after {right: -9.5px !important;}
-#productsBox .addProduct {padding-left: 0px !important; padding-right: 10px;}
-#productsBox .addProduct.required:after {right: -1px !important;}
-#productsBox .input-group-addon > div {display: inline-block !important;}
-#productsBox .required + .text-danger.help-text {position: relative; left: 10px;}
-#productsBox > #productNameLabel {padding-top: 8px;}
-#plansBox .col-sm-4 {float: none; display: inline-block; padding-right: 5px;}
+.productsBox a {border-radius: 2px !important;}
+.productsBox .required:after {right: -9.5px !important;}
+.productsBox .addProduct {padding-left: 0px !important;}
+.productsBox .addProduct.required:after {right: -1px !important;}
+.productsBox .input-group-addon > div {display: inline-block !important;}
+.productsBox .required + .text-danger.help-text {position: relative; left: 10px;}
+.productsBox > #productNameLabel {padding-top: 8px;}
.futureBox {vertical-align: top !important; padding-top: 13px !important;}
#projectType {padding-top: 7px !important;}
.division .icon-help {margin-left: 15px}
-.has-info {border-color: #0c64eb!important;}
\ No newline at end of file
+.has-info {border-color: #0c64eb!important;}
diff --git a/module/project/css/edit.css b/module/project/css/edit.css
index e586511016..12113d5d32 100644
--- a/module/project/css/edit.css
+++ b/module/project/css/edit.css
@@ -5,5 +5,4 @@
#dateRange, .futureBox {vertical-align: top !important; padding-top: 13px !important;}
#endList {vertical-align: top; padding-top: 13px;}
#linkPlan {padding-top: 25px !important;}
-#productsBox .row .col-sm-4.required::after {right: -5px;}
#projectType {padding-top: 7px !important;}
diff --git a/module/project/js/common.js b/module/project/js/common.js
index 9234174496..7af601555d 100644
--- a/module/project/js/common.js
+++ b/module/project/js/common.js
@@ -138,134 +138,6 @@ function computeEndDate(delta)
$('#days').trigger('mousedown');
}
-/**
- * Load branches.
- *
- * @param object product
- * @access public
- * @return void
- */
-function loadBranches(product)
-{
- /* When selecting a product, delete a plan that is empty by default. */
- $("#planDefault").remove();
-
- var chosenProducts = [];
- $("#productsBox select[name^='products']").each(function()
- {
- var $product = $(product);
- var productID = $(this).val();
- if(productID > 0 && chosenProducts.indexOf(productID) == -1) chosenProducts.push(productID);
- if($product.val() != 0 && $product.val() == $(this).val() && $product.attr('id') != $(this).attr('id') && !multiBranchProducts[$product.val()])
- {
- bootbox.alert(errorSameProducts);
- $product.val(0);
- $product.trigger("chosen:updated");
- return false;
- }
- });
-
- chosenProducts.length > 1 ? $('.division').removeClass('hide') : $('.division').addClass('hide');
-
- if($('#productsBox .input-group:last select:first').val() != 0)
- {
- var length = $('#productsBox .row .input-group').size();
- $('#productsBox .row').append('' + $('#productsBox .col-sm-4:last').html() + '
');
- if($('#productsBox .input-group:last select').size() >= 2) $('#productsBox .input-group:last select:last').remove();
- $('#productsBox .input-group:last .chosen-container').remove();
- $('#productsBox .input-group:last select:first').attr('name', 'products[' + length + ']').attr('id', 'products' + length);
- $('#productsBox .input-group:last .chosen').chosen();
-
- adjustProductBoxMargin();
- }
-
- var $inputgroup = $(product).closest('.input-group');
- if($inputgroup.find('select').size() >= 2) $inputgroup.removeClass('has-branch').find('select:last').remove();
- if($inputgroup.find('.chosen-container').size() >= 2) $inputgroup.find('.chosen-container:last').remove();
-
- var index = $inputgroup.find('select:first').attr('id').replace('products' , '');
- var oldBranch = $(product).attr('data-branch') !== undefined ? $(product).attr('data-branch') : 0;
- $.get(createLink('branch', 'ajaxGetBranches', "productID=" + $(product).val() + "&oldBranch=" + oldBranch + "¶m=active"), function(data)
- {
- if(data)
- {
- $inputgroup.addClass('has-branch').append(data);
- $inputgroup.find('select:last').attr('name', 'branch[' + index + ']').attr('id', 'branch' + index).attr('onchange', "loadPlans('#products" + index + "', this.value)").chosen();
-
- $inputgroup.find('select:last').each(disableSelectedBranch);
- disableSelectedProduct();
- }
-
- var branchID = $('#branch' + index).val();
- loadPlans(product, branchID);
- });
-
- if(!multiBranchProducts[$(product).val()]) disableSelectedProduct();
-
- $("input[name='products[" + index + "]']").remove();
- $("input[name='branch[" + index + "]']").remove();
-}
-
-function loadPlans(product, branchID)
-{
- if($('#plansBox').size() == 0) return false;
-
- var productID = $(product).val();
- var branchID = typeof(branchID) == 'undefined' ? 0 : branchID;
- var planID = $(product).attr('data-plan') !== undefined ? $(product).attr('data-plan') : 0;
- var index = $(product).attr('id').replace('products', '');
-
- $("input[name='products[" + index + "]']").remove();
- $("input[name='branch[" + index + "]']").remove();
-
- $.get(createLink('product', 'ajaxGetPlans', "productID=" + productID + '&branch=0,' + branchID + '&planID=' + planID + '&fieldID&needCreate=&expired=unexpired,noclosed¶m=skipParent,multiple'), function(data)
- {
- if(data)
- {
- if($("div#plan" + index).size() == 0) $("#plansBox .row").append('
');
- $("div#plan" + index).html(data).find('select').attr('name', 'plans[' + productID + ']' + '[' + branchID + '][]').attr('id', 'plans' + productID).chosen();
-
- adjustPlanBoxMargin();
- }
- });
-}
-
-/**
- * Adjust the layout of product selection.
- *
- * @access public
- * @return void
- */
-function adjustProductBoxMargin()
-{
- var productRows = Math.ceil($('#productsBox > .row > .col-sm-4').length / 3);
- if(productRows > 1)
- {
- for(i = 1; i <= productRows - 1; i++)
- {
- $('#productsBox .col-sm-4:lt(' + (i * 3) + ')').css('margin-bottom', '10px');
- }
- }
-}
-
-/**
- * Adjust the layout of the plan selection.
- *
- * @access public
- * @return void
- */
-function adjustPlanBoxMargin()
-{
- var planRows = Math.ceil($('#plansBox > .row > .col-sm-4').length / 3);
- if(planRows > 1)
- {
- for(j = 1; j <= planRows - 1; j++)
- {
- $('#plansBox .col-sm-4:lt(' + (j * 3) + ')').css('margin-bottom', '10px');
- }
- }
-}
-
/**
* Initialization operation.
*
diff --git a/module/project/js/create.js b/module/project/js/create.js
index 64daca13ba..8931eac1b6 100644
--- a/module/project/js/create.js
+++ b/module/project/js/create.js
@@ -11,7 +11,7 @@ $(function()
if(hasProduct == 1)
{
var chosenProducts = 0;
- $("#productsBox select[name^='products']").each(function()
+ $(".productsBox select[name^='products']").each(function()
{
if($(this).val() > 0) chosenProducts ++;
});
@@ -29,16 +29,16 @@ $(function()
setAclList($("#parent").val());
- if(typeof(currentPlanID) == 'undefined')
+ if(typeof(currentPlanID) == 'undefined' && copyProjectID == 0)
{
- $('#productsBox select[id^="products"]').each(function()
+ $('.productsBox select[id^="products"]').each(function()
{
- var branchID = 0;
- if($(this).closest('.input-group').find('select[id^="branch"]').size() > 0)
+ var branch = 0;
+ if($(this).closest('.table-row').find('select[name^="branch"]').size() > 0)
{
- var branchID = $(this).closest('.input-group').find('select[id^="branch"]').val();
+ var branch = $(this).closest('.table-row').find('select[name^="branch"]');
}
- loadPlans($(this), branchID);
+ loadPlans($(this), branch);
});
}
@@ -63,7 +63,7 @@ $(function()
$('#daysLabelInfo').remove();
/* Determine whether the products of the same branch are linked. */
- $("#productsBox select[name^='products']").each(function()
+ $(".productsBox select[name^='products']").each(function()
{
var productID = $(this).val();
if(typeof(products[productID]) == 'undefined') products[productID] = new Array();
@@ -98,7 +98,6 @@ $(function()
}
/* Init for copy execution. */
- $("select[id^=branch]").each(disableSelectedBranch);
disableSelectedProduct();
/* Check the all products and branches control when uncheck the product. */
@@ -106,14 +105,10 @@ $(function()
{
if($(this).val() == 0)
{
- $("select[id^='branch']").each(disableSelectedBranch);
-
disableSelectedProduct();
}
});
- $(document).on('change', "select[id^='branch']", disableSelectedBranch);
-
if(copyProjectID > 0 && copyType != 'previous')
{
$('#name').addClass('has-info')
@@ -167,17 +162,16 @@ function setParentProgram(parentProgram)
var productSelectHtml = data.allProducts;
var planSelectHtml = data.plans;
- $('#productsBox .row .col-sm-4 .input-group').each(function(index)
+ $('.productsBox .row .col-sm-6 .input-group').each(function(index)
{
var selectedProduct = $(this).find('[name^=products]').val();
var selectedBranch = $(this).find('[name^=branch]').val();
var selectedPlan = $('#plan' + index ).find('[name^=plans]').val();
- $(this).html(productSelectHtml);
+ $(this).find('[name^=products]').html(productSelectHtml);
$(this).find('[name^=products]').attr('name', 'products[' + index + ']').attr('id', 'products' + index).attr('data-branch', selectedBranch).attr('data-plan', selectedPlan);
$(this).find('[name^=products]').val(selectedProduct).chosen().change();
});
- $('#productsBox .row .col-sm-4:last').find('.input-group').append($('#productsBox .addProduct .input-group:first .input-group-addon').prop('outerHTML'));
}
if(parentProgram != 0)
@@ -195,17 +189,6 @@ function setParentProgram(parentProgram)
});
$('#parent').attr('data-lastSelected', parentProgram);
-
- if(parentProgram != '0')
- {
- $('#productsBox .addProduct .input-group:first').addClass('required');
- $('#productsBox .row .input-group:first').addClass('required');
- }
- else
- {
- $('#productsBox .addProduct .input-group').removeClass('required');
- $('#productsBox .row .input-group').removeClass('required');
- }
}
/**
@@ -232,35 +215,37 @@ function addNewProduct(obj)
if($(obj).attr('checked'))
{
/* Hide product and plan dropdown controls. */
- $('#productsBox .row .col-sm-4').addClass('hidden');
- $('#productsBox .row .col-sm-4 .input-group').find('select').attr('disabled', true).trigger("chosen:updated");
- $('#plansBox').closest('tr').addClass('hidden');
- $('#plansBox .col-sm-4').find('select').attr('disabled', true).trigger("chosen:updated");
+ $('.newLine').addClass('hidden');
+ $('.productsBox .row').addClass('hidden');
+ $('.productsBox .row .input-group').find('select').attr('disabled', true).trigger("chosen:updated");
$('.division').addClass('hide');
/* Displays the input box for creating a product. */
+ $(obj).closest('td').attr('colspan', 1);
$("[name^='newProduct']").prop('checked', true);
$('#productName').removeAttr('disabled', true);
- $('#productsBox .addProduct').removeClass('hidden');
+ $('.productsBox .addProduct').removeClass('hidden');
$('#productTitle').html(productName);
}
else
{
/* Show product and product dropdown controls. */
- $('#productsBox .row .col-sm-4').removeClass('hidden');
- $('#productsBox .row .col-sm-4 .input-group').find('select').removeAttr('disabled').trigger("chosen:updated");
- $('#plansBox').closest('tr').removeClass('hidden');
- $('#plansBox .col-sm-4').find('select').removeAttr('disabled', true).trigger("chosen:updated");
- if($('#plansBox .col-sm-4').find('select').length > 1) $('.division').removeClass('hide');
+ $('.newLine').removeClass('hidden');
+ $('.productsBox .row').removeClass('hidden');
+ $('.productsBox .row .input-group').find('select').removeAttr('disabled').trigger("chosen:updated");
+ if($('.productsBox').find("select[name^=products]").length > 1) $('.division').removeClass('hide');
/* Hide the input box for creating a product. */
+ $(obj).closest('td').attr('colspan', 3);
$("[name^='newProduct']").prop('checked', false);
$('#productName').attr('disabled', true);
- $('#productsBox .addProduct').addClass('hidden');
+ $('.productsBox .addProduct').addClass('hidden');
- $('#productTitle').html(manageProducts);
+ $('#productTitle').html(manageProductPlan);
}
- $('#productsBox div + .text-danger.help-text').remove();
+
+ $('.productsBox div + .text-danger.help-text').remove();
+ disableSelectedProduct();
}
/**
@@ -295,7 +280,7 @@ function loadBranches(product)
$("#planDefault").remove();
var chosenProducts = [];
- $("#productsBox select[name^='products']").each(function()
+ $(".productsBox select[name^='products']").each(function()
{
var $product = $(product);
var productID = $(this).val();
@@ -311,111 +296,111 @@ function loadBranches(product)
(chosenProducts.length > 1 && model == 'waterfall') ? $('.division').removeClass('hide') : $('.division').addClass('hide');
- if($('#productsBox .row .input-group:last select:first').val() != 0)
+ var $tableRow = $(product).closest('.table-row');
+ var index = $tableRow.find('select:first').attr('id').replace('products' , '');
+ var oldBranch = $(product).attr('data-branch') !== undefined ? $(product).attr('data-branch') : 0;
+ if($(product).val() != 0)
{
- var length = $('#productsBox .row .input-group').size();
- var $html = $('#productsBox .row .col-sm-4:last').html().replace('required', '');
- $('#productsBox .row .col-sm-4:last').find('.input-group-addon').remove();
- $('#productsBox .row').append('' + $html + '
');
- if($('#productsBox .row .input-group:last select').size() >= 2) $('#productsBox .row .input-group:last select:last').remove();
- $('#productsBox .row .input-group:last .chosen-container').remove();
- $('#productsBox .row .input-group:last select:first').attr('name', 'products[' + length + ']').attr('id', 'products' + length);
- $('#productsBox .row .input-group:last .chosen').chosen();
-
- $('[data-toggle="popover"]').popover();
-
- adjustProductBoxMargin();
+ $(product).closest('tr').find('.newProduct').addClass('hidden')
+ }
+ else
+ {
+ $(product).closest('tr').find('.newProduct').removeClass('hidden')
}
- var $inputgroup = $(product).closest('.input-group');
- if($inputgroup.find('select').size() >= 2) $inputgroup.removeClass('has-branch').find('select:last').remove();
- if($inputgroup.find('.chosen-container').size() >= 2) $inputgroup.find('.chosen-container:last').remove();
+ if(!multiBranchProducts[$(product).val()])
+ {
+ $tableRow.find('.table-col:last select').val('').trigger('chosen:updated');
+ $tableRow.find('.table-col:last').addClass('hidden');
+ }
- var index = $inputgroup.find('select:first').attr('id').replace('products' , '');
- var oldBranch = $(product).attr('data-branch') !== undefined ? $(product).attr('data-branch') : 0;
$.get(createLink('branch', 'ajaxGetBranches', "productID=" + $(product).val() + "&oldBranch=" + oldBranch + "¶m=active"), function(data)
{
if(data)
{
- $inputgroup.addClass('has-branch').append(data);
- $inputgroup.find('select:last').attr('name', 'branch[' + index + ']').attr('id', 'branch' + index).attr('onchange', "loadPlans('#products" + index + "', this.value)").chosen();
+ $tableRow.find("select[name^='branch']").replaceWith(data);
+ $tableRow.find('.table-col:last .chosen-container').remove();
+ $tableRow.find('.table-col:last').removeClass('hidden');
+ $tableRow.find("select[name^='branch']").attr('multiple', '').attr('name', 'branch[' + index + '][]').attr('id', 'branch' + index).attr('onchange', "loadPlans('#products" + index + "', this)").chosen();
- $inputgroup.find('select:last').each(disableSelectedBranch);
disableSelectedProduct();
}
- var branchID = $('#branch' + index).val();
- loadPlans(product, branchID);
+ var branch = $('#branch' + index);
+ loadPlans(product, branch);
});
-
- if(!multiBranchProducts[$(product).val()]) disableSelectedProduct();
}
/**
* Load plans.
*
* @param obj $product
- * @param int $branchID
+ * @param obj $branchID
* @access public
* @return void
*/
-function loadPlans(product, branchID)
+function loadPlans(product, branch)
{
- if($('#plansBox').size() == 0) return false;
-
var productID = $(product).val();
- var branchID = typeof(branchID) == 'undefined' ? 0 : branchID;
+ var branchID = $(branch).val() == null ? 0 : '0,' + $(branch).val();
var planID = $(product).attr('data-plan') !== undefined ? $(product).attr('data-plan') : 0;
var index = $(product).attr('id').replace('products', '');
- $.get(createLink('product', 'ajaxGetPlans', "productID=" + productID + '&branch=0,' + branchID + '&planID=' + planID + '&fieldID&needCreate=&expired=unexpired,noclosed¶m=skipParent,multiple'), function(data)
+ $.get(createLink('product', 'ajaxGetPlans', "productID=" + productID + '&branch=' + branchID + '&planID=' + planID + '&fieldID&needCreate=&expired=unexpired,noclosed¶m=skipParent,multiple'), function(data)
{
if(data)
{
- if($("div#plan" + index).size() == 0) $("#plansBox .row").append('
');
- $("div#plan" + index).html(data).find('select').attr('name', 'plans[' + productID + '][' + branchID + '][]').attr('id', 'plans' + productID).chosen();
-
- adjustPlanBoxMargin();
+ $("div#plan" + index).find("select[name^='plans']").replaceWith(data);
+ $("div#plan" + index).find('.chosen-container').remove();
+ $("div#plan" + index).find('select').attr('name', 'plans[' + productID + ']' + '[]').attr('id', 'plans' + productID).chosen();
}
});
}
/**
- * Adjust product box margin.
- *
+ * Add new line for link product.
+ *
+ * @param obj $obj
* @access public
* @return void
*/
-function adjustProductBoxMargin()
+function addNewLine(obj)
{
- var productRows = Math.ceil($('#productsBox > .row > .col-sm-4').length / 3);
- if(productRows > 1)
+ var newLine = $(obj).closest('tr').clone();
+ var index = 0;
+ $(".productsBox select[name^='products']").each(function()
{
- for(i = 1; i <= productRows - 1; i++)
- {
- $('#productsBox .row .col-sm-4:lt(' + (i * 3) + ')').css('margin-bottom', '10px');
- $('#productsBox .row .col-sm-4').eq(i * 3).css('padding-right', '6px');
- }
- }
+ var id = $(this).attr('id').replace('products' , '');
+
+ id = parseInt(id);
+ id ++;
+
+ index = id > index ? id : index;
+ })
+
+ newLine.find('.newProduct').remove();
+ newLine.find('.addProduct').remove();
+ newLine.addClass('newLine');
+ newLine.find('th').html('');
+ newLine.find('.removeLine').css('visibility', 'visible');
+ newLine.find('.chosen-container').remove();
+ newLine.find('.productsBox .table-col:last').addClass('hidden');
+ newLine.find("select[name^='products']").attr('name', 'products[' + index + ']').attr('id', 'products' + index).val('').chosen();
+ newLine.find("select[name^='plans']").attr('name', 'plans[' + index + '][' + 0 + '][]').chosen();
+ newLine.find("div[id^='plan']").attr('id', 'plan' + index);
+
+ $(obj).closest('tr').after(newLine);
+ var product = newLine.find("select[name^='products']");
+ var branch = newLine.find("select[name^='branch']");
+ loadPlans(product, branch);
+ disableSelectedProduct();
}
-/**
- * Adjust plan box margin.
- *
- * @access public
- * @return void
- */
-function adjustPlanBoxMargin()
+function removeLine(obj)
{
- var planRows = Math.ceil($('#plansBox > .row > .col-sm-4').length / 3);
- if(planRows > 1)
- {
- for(j = 1; j <= planRows - 1; j++)
- {
- $('#plansBox .col-sm-4:lt(' + (j * 3) + ')').css('margin-bottom', '10px');
- $('#plansBox .col-sm-4').eq(j * 3).css('padding-right', '6px');
- }
- }
+ $(obj).closest('tr').remove();
+ disableSelectedProduct();
+ if($("select[name^='products']").length < 2) $('.division').addClass('hide');
}
/**
diff --git a/module/project/js/edit.js b/module/project/js/edit.js
index 73d391e9c5..c1353908a1 100644
--- a/module/project/js/edit.js
+++ b/module/project/js/edit.js
@@ -18,13 +18,13 @@ $(function()
var productSelectHtml = data.allProducts;
var planSelectHtml = data.plans;
- $('#productsBox .row .col-sm-4 .input-group').each(function(index)
+ $('.productsBox .row .col-sm-6 .input-group').each(function(index)
{
var selectedProduct = $(this).find('[name^=products]').val();
var selectedBranch = $(this).find('[name^=branch]').val();
var selectedPlan = $('#plan' + index ).find('[name^=plans]').val();
- $(this).html(productSelectHtml);
+ $(this).find('[name^=products]').html(productSelectHtml);
$(this).find('[name^=products]').attr('name', 'products[' + index + ']').attr('id', 'products' + index).attr('data-branch', selectedBranch).attr('data-plan', selectedPlan);
$(this).find('[name^=products]').val(selectedProduct).chosen().change();
});
@@ -32,9 +32,6 @@ $(function()
});
});
- adjustProductBoxMargin();
- adjustPlanBoxMargin();
-
$(document).on('change', '[name*=products]', function()
{
var current = $(this).val();
@@ -173,3 +170,129 @@ function setAclList(programID)
$('.aclBox').html($('#projectAcl').html());
}
}
+
+/**
+ * Load branches.
+ *
+ * @param int $product
+ * @access public
+ * @return void
+ */
+function loadBranches(product)
+{
+ /* When selecting a product, delete a plan that is empty by default. */
+ $("#planDefault").remove();
+
+ var chosenProducts = [];
+ $(".productsBox select[name^='products']").each(function()
+ {
+ var $product = $(product);
+ var productID = $(this).val();
+ if(productID > 0 && chosenProducts.indexOf(productID) == -1) chosenProducts.push(productID);
+ if($product.val() != 0 && $product.val() == $(this).val() && $product.attr('id') != $(this).attr('id') && !multiBranchProducts[$product.val()])
+ {
+ bootbox.alert(errorSameProducts);
+ $product.val(0);
+ $product.trigger("chosen:updated");
+ return false;
+ }
+ });
+
+ (chosenProducts.length > 1 && model == 'waterfall') ? $('.division').removeClass('hide') : $('.division').addClass('hide');
+
+ var $tableRow = $(product).closest('.table-row');
+ var index = $tableRow.find('select:first').attr('id').replace('products' , '');
+ var oldBranch = $(product).attr('data-branch') !== undefined ? $(product).attr('data-branch') : 0;
+
+ if(!multiBranchProducts[$(product).val()])
+ {
+ $tableRow.find('.table-col:last select').val('').trigger('chosen:updated');
+ $tableRow.find('.table-col:last').addClass('hidden');
+ }
+
+ $.get(createLink('branch', 'ajaxGetBranches', "productID=" + $(product).val() + "&oldBranch=" + oldBranch + "¶m=active"), function(data)
+ {
+ if(data)
+ {
+ $tableRow.find("select[name^='branch']").replaceWith(data);
+ $tableRow.find('.table-col:last .chosen-container').remove();
+ $tableRow.find('.table-col:last').removeClass('hidden');
+ $tableRow.find("select[name^='branch']").attr('multiple', '').attr('name', 'branch[' + index + '][]').attr('id', 'branch' + index).attr('onchange', "loadPlans('#products" + index + "', this)").chosen();
+
+ disableSelectedProduct();
+ }
+
+ var branch = $('#branch' + index);
+ loadPlans(product, branch);
+ });
+}
+
+/**
+ * Load plans.
+ *
+ * @param obj $product
+ * @param obj $branchID
+ * @access public
+ * @return void
+ */
+function loadPlans(product, branch)
+{
+ var productID = $(product).val();
+ var branchID = $(branch).val() == null ? 0 : '0,' + $(branch).val();
+ var planID = $(product).attr('data-plan') !== undefined ? $(product).attr('data-plan') : 0;
+ var index = $(product).attr('id').replace('products', '');
+
+ $.get(createLink('product', 'ajaxGetPlans', "productID=" + productID + '&branch=' + branchID + '&planID=' + planID + '&fieldID&needCreate=&expired=unexpired,noclosed¶m=skipParent,multiple'), function(data)
+ {
+ if(data)
+ {
+ $("div#plan" + index).find("select[name^='plans']").replaceWith(data);
+ $("div#plan" + index).find('.chosen-container').remove();
+ $("div#plan" + index).find('select').attr('name', 'plans[' + productID + ']' + '[]').attr('id', 'plans' + productID).chosen();
+ }
+ });
+}
+
+/**
+ * Add new line for link product.
+ *
+ * @param obj $obj
+ * @access public
+ * @return void
+ */
+function addNewLine(obj)
+{
+ var newLine = $(obj).closest('tr').clone();
+ var index = 0;
+ $(".productsBox select[name^='products']").each(function()
+ {
+ var id = $(this).attr('id').replace('products' , '');
+
+ id = parseInt(id);
+ id ++;
+
+ index = id > index ? id : index;
+ })
+
+ newLine.addClass('newLine');
+ newLine.find('th').html('');
+ newLine.find('.removeLine').css('visibility', 'visible');
+ newLine.find('.chosen-container').remove();
+ newLine.find('.productsBox .table-col:last').addClass('hidden');
+ newLine.find("select[name^='products']").attr('name', 'products[' + index + ']').attr('id', 'products' + index).val('').chosen();
+ newLine.find("select[name^='plans']").attr('name', 'plans[' + index + '][' + 0 + '][]').chosen();
+ newLine.find("div[id^='plan']").attr('id', 'plan' + index);
+
+ $(obj).closest('tr').after(newLine);
+ var product = newLine.find("select[name^='products']");
+ var branch = newLine.find("select[name^='branch']");
+ loadPlans(product, branch);
+ disableSelectedProduct();
+}
+
+function removeLine(obj)
+{
+ $(obj).closest('tr').remove();
+ disableSelectedProduct();
+ if($("select[name^='products']").length < 2) $('.division').addClass('hide');
+}
diff --git a/module/project/lang/de.php b/module/project/lang/de.php
index e40046ff4f..9f10afb9ee 100644
--- a/module/project/lang/de.php
+++ b/module/project/lang/de.php
@@ -42,6 +42,7 @@ $lang->project->addWhitelist = 'Project Add Whitelist';
$lang->project->unbindWhitelist = 'Project Remove Whitelist';
$lang->project->manageProducts = 'Manage Products';
$lang->project->manageOtherProducts = 'Manage Other Products';
+$lang->project->manageProductPlan = 'Manage Products And Plans';
$lang->project->copyTitle = 'Please select an project to copy';
$lang->project->errorSameProducts = 'Project cannot be associated with multiple identical products.';
$lang->project->errorSameBranches = 'Project cannot be associated with multiple identical branches.';
@@ -168,14 +169,14 @@ $lang->project->acTitle = 'Actual Cost';
$lang->project->cvTitle = 'Cost Variance';
$lang->project->teamCount = 'Man';
$lang->project->teamSumCount = '%s people in total';
-$lang->project->longTime = 'Long-Term Program';
+$lang->project->longTime = 'Long-Term';
$lang->project->future = 'TBD';
$lang->project->moreProject = 'More Project';
$lang->project->days = 'Manntage';
$lang->project->mailto = 'Mailto';
$lang->project->etc = " , etc";
$lang->project->product = 'Product';
-$lang->project->branch = 'Branch';
+$lang->project->branch = 'Platform/Branch';
$lang->project->plan = 'Plan';
$lang->project->createKanban = 'Create Kanban';
$lang->project->kanban = 'Kanban';
@@ -338,6 +339,7 @@ $lang->project->scrum = 'Scrum';
$lang->project->waterfall = 'CMMI';
$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->emptyPM = 'No manager';
+$lang->project->emptyBranch = 'Branch can not be empty!';
$lang->project->cannotChangeToCat = "It is not empty, so you cannot change it to a parent.";
$lang->project->cannotCancelCat = "It has child projects, so you cannot unmark the parent.";
$lang->project->parentBeginEnd = "Parent begin&end date: %s ~ %s";
@@ -379,4 +381,7 @@ $lang->project->copyProject = new stdClass();
$lang->project->copyProject->nameTips = '『Project Name』Cannot be repeated.';
$lang->project->copyProject->codeTips = '『Project Code』Cannot be repeated.';
$lang->project->copyProject->endTips = '『Schedule End』Cannot be empty.';
-$lang->project->copyProject->daysTips = '『Available working days』Should be numerical.';
\ No newline at end of file
+$lang->project->copyProject->daysTips = '『Available working days』Should be numerical.';
+
+$lang->project->linkBranchStoryByPlanTips = "When the project is scheduled to associate requirements, only the activated requirements associated with the %s of the project are imported.";
+$lang->project->linkNormalStoryByPlanTips = "When the project plans to associate requirements, only the requirements that are active are imported.";
\ No newline at end of file
diff --git a/module/project/lang/en.php b/module/project/lang/en.php
index d8d7ca0258..291bff2889 100644
--- a/module/project/lang/en.php
+++ b/module/project/lang/en.php
@@ -42,6 +42,7 @@ $lang->project->addWhitelist = 'Project Add Whitelist';
$lang->project->unbindWhitelist = 'Project Remove Whitelist';
$lang->project->manageProducts = 'Manage Products';
$lang->project->manageOtherProducts = 'Manage Other Products';
+$lang->project->manageProductPlan = 'Manage Products And Plans';
$lang->project->copyTitle = 'Please select an project to copy';
$lang->project->errorSameProducts = 'Project cannot be associated with multiple identical products.';
$lang->project->errorSameBranches = 'Project cannot be associated with multiple identical branches.';
@@ -168,14 +169,14 @@ $lang->project->acTitle = 'Actual Cost';
$lang->project->cvTitle = 'Cost Variance';
$lang->project->teamCount = 'Team';
$lang->project->teamSumCount = '%s people in total';
-$lang->project->longTime = 'Long-Term Program';
+$lang->project->longTime = 'Long-Term';
$lang->project->future = 'TBD';
$lang->project->moreProject = 'More Project';
$lang->project->days = 'Days';
$lang->project->mailto = 'Mailto';
$lang->project->etc = " , etc";
$lang->project->product = 'Product';
-$lang->project->branch = 'Branch';
+$lang->project->branch = 'Platform/Branch';
$lang->project->plan = 'Plan';
$lang->project->createKanban = 'Create Kanban';
$lang->project->kanban = 'Kanban';
@@ -338,6 +339,7 @@ $lang->project->scrum = 'Scrum';
$lang->project->waterfall = 'Waterfall';
$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->emptyPM = 'No manager';
+$lang->project->emptyBranch = 'Branch can not be empty!';
$lang->project->cannotChangeToCat = "It is not empty, so you cannot change it to a parent.";
$lang->project->cannotCancelCat = "It has child projects, so you cannot unmark the parent.";
$lang->project->parentBeginEnd = "Parent begin&end date: %s ~ %s";
@@ -379,4 +381,7 @@ $lang->project->copyProject = new stdClass();
$lang->project->copyProject->nameTips = '『Project Name』Cannot be repeated.';
$lang->project->copyProject->codeTips = '『Project Code』Cannot be repeated.';
$lang->project->copyProject->endTips = '『Schedule End』Cannot be empty.';
-$lang->project->copyProject->daysTips = '『Available working days』Should be numerical.';
\ No newline at end of file
+$lang->project->copyProject->daysTips = '『Available working days』Should be numerical.';
+
+$lang->project->linkBranchStoryByPlanTips = "When the project is scheduled to associate requirements, only the activated requirements associated with the %s of the project are imported.";
+$lang->project->linkNormalStoryByPlanTips = "When the project plans to associate requirements, only the requirements that are active are imported.";
\ No newline at end of file
diff --git a/module/project/lang/fr.php b/module/project/lang/fr.php
index bbb06e432f..55285fa342 100644
--- a/module/project/lang/fr.php
+++ b/module/project/lang/fr.php
@@ -42,6 +42,7 @@ $lang->project->addWhitelist = 'Project Add Whitelist';
$lang->project->unbindWhitelist = 'Project Remove Whitelist';
$lang->project->manageProducts = 'Manage Products';
$lang->project->manageOtherProducts = 'Manage Other Products';
+$lang->project->manageProductPlan = 'Manage Products And Plans';
$lang->project->copyTitle = 'Please select an project to copy';
$lang->project->errorSameProducts = 'Project cannot be associated with multiple identical products.';
$lang->project->errorSameBranches = 'Project cannot be associated with multiple identical branches.';
@@ -168,14 +169,14 @@ $lang->project->acTitle = 'Actual Cost';
$lang->project->cvTitle = 'Cost Variance';
$lang->project->teamCount = 'Man';
$lang->project->teamSumCount = '%s people in total';
-$lang->project->longTime = 'Long-Term Program';
+$lang->project->longTime = 'Long-Term';
$lang->project->future = 'TBD';
$lang->project->moreProject = 'More Project';
$lang->project->days = 'Available Days';
$lang->project->mailto = 'Mailto';
$lang->project->etc = " , etc";
$lang->project->product = 'Product';
-$lang->project->branch = 'Branch';
+$lang->project->branch = 'Platform/Branch';
$lang->project->plan = 'Plan';
$lang->project->createKanban = 'Create Kanban';
$lang->project->kanban = 'Kanban';
@@ -338,6 +339,7 @@ $lang->project->scrum = 'Scrum';
$lang->project->waterfall = 'CMMI';
$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->emptyPM = 'No manager';
+$lang->project->emptyBranch = 'Branch can not be empty!';
$lang->project->cannotChangeToCat = "It is not empty, so you cannot change it to a parent.";
$lang->project->cannotCancelCat = "It has child projects, so you cannot unmark the parent.";
$lang->project->parentBeginEnd = "Parent begin&end date: %s ~ %s";
@@ -379,4 +381,7 @@ $lang->project->copyProject = new stdClass();
$lang->project->copyProject->nameTips = '『Project Name』Cannot be repeated.';
$lang->project->copyProject->codeTips = '『Project Code』Cannot be repeated.';
$lang->project->copyProject->endTips = '『Schedule End』Cannot be empty.';
-$lang->project->copyProject->daysTips = '『Available working days』Should be numerical.';
\ No newline at end of file
+$lang->project->copyProject->daysTips = '『Available working days』Should be numerical.';
+
+$lang->project->linkBranchStoryByPlanTips = "When the project is scheduled to associate requirements, only the activated requirements associated with the %s of the project are imported.";
+$lang->project->linkNormalStoryByPlanTips = "When the project plans to associate requirements, only the requirements that are active are imported.";
\ No newline at end of file
diff --git a/module/project/lang/vi.php b/module/project/lang/vi.php
index e6b35cab56..ef618f941a 100644
--- a/module/project/lang/vi.php
+++ b/module/project/lang/vi.php
@@ -42,6 +42,7 @@ $lang->project->addWhitelist = 'Project Add Whitelist';
$lang->project->unbindWhitelist = 'Project Remove Whitelist';
$lang->project->manageProducts = 'Manage Products';
$lang->project->manageOtherProducts = 'Manage Other Products';
+$lang->project->manageProductPlan = 'Manage Products And Plans';
$lang->project->copyTitle = 'Please select an project to copy';
$lang->project->errorSameProducts = 'Project cannot be associated with multiple identical products.';
$lang->project->errorSameBranches = 'Project cannot be associated with multiple identical branches.';
@@ -175,7 +176,7 @@ $lang->project->days = 'Days';
$lang->project->mailto = 'Mailto';
$lang->project->etc = " , etc";
$lang->project->product = 'Product';
-$lang->project->branch = 'Branch';
+$lang->project->branch = 'Platform/Branch';
$lang->project->plan = 'Plan';
$lang->project->createKanban = 'Create Kanban';
$lang->project->kanban = 'Kanban';
@@ -335,6 +336,7 @@ $lang->project->scrum = 'Scrum';
$lang->project->waterfall = 'Waterfall';
$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->emptyPM = 'No manager';
+$lang->project->emptyBranch = 'Branch can not be empty!';
$lang->project->cannotChangeToCat = "It is not empty, so you cannot change it to a parent.";
$lang->project->cannotCancelCat = "It has child projects, so you cannot unmark the parent.";
$lang->project->parentBeginEnd = "Parent begin&end date: %s ~ %s";
diff --git a/module/project/lang/zh-cn.php b/module/project/lang/zh-cn.php
index e8f21c9b32..b7dfef7811 100644
--- a/module/project/lang/zh-cn.php
+++ b/module/project/lang/zh-cn.php
@@ -42,6 +42,7 @@ $lang->project->addWhitelist = '项目添加白名单';
$lang->project->unbindWhitelist = '项目删除白名单';
$lang->project->manageProducts = '关联产品';
$lang->project->manageOtherProducts = '关联其他产品';
+$lang->project->manageProductPlan = '关联产品和计划';
$lang->project->copyTitle = '请选择要复制的项目';
$lang->project->errorSameProducts = '项目不能关联多个相同的产品。';
$lang->project->errorSameBranches = '项目不能关联多个相同的分支。';
@@ -175,7 +176,7 @@ $lang->project->days = '可用工作日';
$lang->project->mailto = '抄送给';
$lang->project->etc = "等";
$lang->project->product = '所属产品';
-$lang->project->branch = '所属分支';
+$lang->project->branch = '平台/分支';
$lang->project->plan = '所属计划';
$lang->project->createKanban = '添加看板';
$lang->project->kanban = '看板';
@@ -233,7 +234,7 @@ $lang->project->hundredMillion = '亿';
$lang->project->unitList['CNY'] = '人民币';
$lang->project->unitList['USD'] = '美元';
$lang->project->unitList['HKD'] = '港元';
-$lang->project->unitList['NTD'] = '台元';
+$lang->project->unitList['NTD'] = '台币';
$lang->project->unitList['EUR'] = '欧元';
$lang->project->unitList['DEM'] = '马克';
$lang->project->unitList['CHF'] = '瑞士法郎';
@@ -338,6 +339,7 @@ $lang->project->scrum = 'Scrum';
$lang->project->waterfall = '瀑布';
$lang->project->cannotCreateChild = '该项目已经有实际的内容,无法直接添加子项目。您可以为当前项目创建一个父项目,然后在新的父项目下面添加子项目。';
$lang->project->emptyPM = '暂无';
+$lang->project->emptyBranch = '分支不能为空!';
$lang->project->cannotChangeToCat = "该项目已经有实际的内容,无法修改为父项目";
$lang->project->cannotCancelCat = "该项目下已经有子项目,无法取消父项目标记";
$lang->project->parentBeginEnd = "父项目起止时间:%s ~ %s";
@@ -379,4 +381,7 @@ $lang->project->copyProject = new stdClass();
$lang->project->copyProject->nameTips = '『项目名称』不可重复需要修改。';
$lang->project->copyProject->codeTips = '『项目代号』不可重复需要修改。';
$lang->project->copyProject->endTips = '『计划完成』不能为空。';
-$lang->project->copyProject->daysTips = '『可用工作日』应当是数字。';
\ No newline at end of file
+$lang->project->copyProject->daysTips = '『可用工作日』应当是数字。';
+
+$lang->project->linkBranchStoryByPlanTips = "项目按计划关联需求时,只导入本项目所关联%s的激活状态的需求。";
+$lang->project->linkNormalStoryByPlanTips = "项目按计划关联需求时,只导入激活状态的需求。";
diff --git a/module/project/lang/zh-tw.php b/module/project/lang/zh-tw.php
index 454946432b..ceafb35e2c 100644
--- a/module/project/lang/zh-tw.php
+++ b/module/project/lang/zh-tw.php
@@ -190,7 +190,7 @@ $lang->project->tenThousand = '萬';
$lang->project->unitList['CNY'] = '人民幣';
$lang->project->unitList['USD'] = '美元';
$lang->project->unitList['HKD'] = '港元';
-$lang->project->unitList['NTD'] = '台元';
+$lang->project->unitList['NTD'] = '台幣';
$lang->project->unitList['EUR'] = '歐元';
$lang->project->unitList['DEM'] = '馬克';
$lang->project->unitList['CHF'] = '瑞士法郎';
diff --git a/module/project/model.php b/module/project/model.php
index 4718eb0b9b..46d27fcb6d 100644
--- a/module/project/model.php
+++ b/module/project/model.php
@@ -753,11 +753,14 @@ class projectModel extends model
public function getStatData($projectID)
{
$executions = $this->loadModel('execution')->getPairs($projectID);
- $storyCount = $this->dao->select('count(t2.story) as storyCount')->from(TABLE_STORY)->alias('t1')
+ $storyTypeCount = $this->dao->select('count(t2.story) as storyCount,t1.type')->from(TABLE_STORY)->alias('t1')
->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id = t2.story')
->where('t2.project')->eq($projectID)
->andWhere('t1.deleted')->eq(0)
- ->fetch('storyCount');
+ ->groupBy('t1.type')
+ ->fetchPairs('type', 'storyCount');
+ $storyCount = isset($storyTypeCount['story']) ? $storyTypeCount['story'] : 0;
+ $requirementCount = isset($storyTypeCount['requirement']) ? $storyTypeCount['requirement'] : 0;
$taskCount = $this->dao->select('count(id) as taskCount')->from(TABLE_TASK)->where('execution')->in(array_keys($executions))->andWhere('deleted')->eq(0)->fetch('taskCount');
$bugCount = $this->dao->select('count(id) as bugCount')->from(TABLE_BUG)->where('project')->in($projectID)->andWhere('deleted')->eq(0)->fetch('bugCount');
@@ -773,13 +776,14 @@ class projectModel extends model
->fetch('count');
$statData = new stdclass();
- $statData->storyCount = $storyCount;
- $statData->taskCount = $taskCount;
- $statData->bugCount = $bugCount;
- $statData->waitCount = zget($statusPairs, 'wait', 0);
- $statData->doingCount = zget($statusPairs, 'doing', 0);
- $statData->finishedCount = $finishedCount;
- $statData->delayedCount = $delayedCount;
+ $statData->storyCount = $storyCount;
+ $statData->requirementCount = $requirementCount;
+ $statData->taskCount = $taskCount;
+ $statData->bugCount = $bugCount;
+ $statData->waitCount = zget($statusPairs, 'wait', 0);
+ $statData->doingCount = zget($statusPairs, 'doing', 0);
+ $statData->finishedCount = $finishedCount;
+ $statData->delayedCount = $delayedCount;
return $statData;
}
@@ -1243,6 +1247,20 @@ class projectModel extends model
}
}
+ if($_POST['products'])
+ {
+ $topProgramID = $this->loadModel('program')->getTopByID($project->parent);
+ $multipleProducts = $this->loadModel('product')->getMultiBranchPairs($topProgramID);
+ foreach($_POST['products'] as $index => $productID)
+ {
+ if(isset($multipleProducts[$productID]) and empty($_POST['branch'][$index]))
+ {
+ dao::$errors[] = $this->lang->project->emptyBranch;
+ return false;
+ }
+ }
+ }
+
$program = new stdClass();
if($project->parent)
{
@@ -1485,6 +1503,7 @@ class projectModel extends model
->setDefault('team', $this->post->name)
->setDefault('lastEditedBy', $this->app->user->account)
->setDefault('lastEditedDate', helper::now())
+ ->setDefault('parent', 0)
->setIF($this->post->delta == 999, 'end', LONG_TIME)
->setIF($this->post->delta == 999, 'days', 0)
->setIF($this->post->begin == '0000-00-00', 'begin', '')
@@ -1508,12 +1527,27 @@ class projectModel extends model
if(dao::isError()) return false;
}
+ if($_POST['products'])
+ {
+ $topProgramID = $this->loadModel('program')->getTopByID($project->parent);
+ $multipleProducts = $this->loadModel('product')->getMultiBranchPairs($topProgramID);
+ foreach($_POST['products'] as $index => $productID)
+ {
+ if(isset($multipleProducts[$productID]) and empty($_POST['branch'][$index]))
+ {
+ dao::$errors[] = $this->lang->project->emptyBranch;
+ return false;
+ }
+ }
+ }
+
/* Judge products not empty. */
$linkedProductsCount = 0;
foreach($_POST['products'] as $product)
{
if(!empty($product)) $linkedProductsCount++;
}
+
if(empty($linkedProductsCount))
{
dao::$errors[] = $this->lang->project->errorNoProducts;
@@ -1553,7 +1587,7 @@ class projectModel extends model
->exec();
if(dao::isError()) return false;
- if(!$oldProject->hasProduct && $oldProject->name != $project->name) $this->updateProductName($project->name, $projectID);
+ if(!$oldProject->hasProduct and ($oldProject->name != $project->name or $oldProject->parent != $project->parent or $oldProject->acl != $project->acl)) $this->updateShadowProduct($project);
/* Get team and language item. */
$this->loadModel('user');
@@ -1714,7 +1748,7 @@ class projectModel extends model
if(!dao::isError())
{
- if(!$oldProject->hasProduct && $oldProject->name != $project->name) $this->updateProductName($project->name, $projectID);
+ if(!$oldProject->hasProduct and ($oldProject->name != $project->name or $oldProject->parent != $project->parent or $oldProject->acl != $project->acl)) $this->updateShadowProduct($project);
if(isset($project->parent))
{
@@ -2008,17 +2042,18 @@ class projectModel extends model
}
/**
- * Update a product's name as its project.
+ * Update shadow product for its project.
*
- * @param string $projectName
- * @param int $projectID
+ * @param object $project
* @access public
* @return bool
*/
- public function updateProductName($projectName, $projectID)
+ public function updateShadowProduct($project)
{
- $product = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->fetch('product');
- $this->dao->update(TABLE_PRODUCT)->set('name')->eq($projectName)->where('id')->eq($product)->exec();
+ $product = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($project->id)->fetch('product');
+ $topProgram = !empty($project->parent) ? $this->loadModel('program')->getTopByID($project->parent) : 0;
+ $this->dao->update(TABLE_PRODUCT)->set('name')->eq($project->name)->set('program')->eq($topProgram)->set('acl')->eq($project->acl)->where('id')->eq($product)->exec();
+
return !dao::isError();
}
@@ -2403,24 +2438,29 @@ class projectModel extends model
$oldPlan = 0;
$branch = isset($branches[$i]) ? $branches[$i] : 0;
- if(isset($existedProducts[$productID][$branch])) continue;
+ if(!is_array($branch)) $branch = array($branch => $branch);
- if(isset($oldProjectProducts[$productID][$branch]))
+ foreach($branch as $branchID)
{
- $oldProjectProduct = $oldProjectProducts[$productID][$branch];
- if($this->app->rawMethod != 'edit') $oldPlan = $oldProjectProduct->plan;
+ if(isset($existedProducts[$productID][$branchID])) continue;
+
+ if(isset($oldProjectProducts[$productID][$branchID]))
+ {
+ $oldProjectProduct = $oldProjectProducts[$productID][$branchID];
+ if($this->app->rawMethod != 'edit') $oldPlan = $oldProjectProduct->plan;
+ }
+
+ $data = new stdclass();
+ $data->project = $projectID;
+ $data->product = $productID;
+ $data->branch = $branchID;
+ $data->plan = isset($plans[$productID]) ? implode(',', $plans[$productID]) : $oldPlan;
+ $data->plan = trim($data->plan, ',');
+ $data->plan = empty($data->plan) ? 0 : ",$data->plan,";
+
+ $this->dao->insert(TABLE_PROJECTPRODUCT)->data($data)->exec();
+ $existedProducts[$productID][$branchID] = true;
}
-
- $data = new stdclass();
- $data->project = $projectID;
- $data->product = $productID;
- $data->branch = $branch;
- $data->plan = isset($plans[$productID][$branch]) ? implode(',', $plans[$productID][$branch]) : $oldPlan;
- $data->plan = trim($data->plan, ',');
- $data->plan = empty($data->plan) ? 0 : ",$data->plan,";
-
- $this->dao->insert(TABLE_PROJECTPRODUCT)->data($data)->exec();
- $existedProducts[$productID][$branch] = true;
}
/* Delete the execution linked products that is not linked with the execution. */
diff --git a/module/project/view/build.html.php b/module/project/view/build.html.php
index e38be60903..58aeb65870 100644
--- a/module/project/view/build.html.php
+++ b/module/project/view/build.html.php
@@ -51,14 +51,16 @@
build->name;?>
hasProduct):?>
build->product;?>
+
+ build->branch;?>
+
multiple):?>
- executionCommon;?>
+ executionCommon;?>
- build->scmPath;?>
- build->filePath;?>
- build->date;?>
- build->builder;?>
+ build->url;?>
+ build->date;?>
+ build->builder;?>
actions;?>
@@ -69,18 +71,27 @@
id"), sprintf('%03d', $build->id), '', "data-app='project'");?>
- execution):?>
-
-
- branchName) echo "{$build->branchName} "?>
createLink($module, 'view', "buildID=$build->id"), $build->name, '', "data-app='project'");?>
+ execution):?>
+
+
hasProduct):?>
productName;?>
+
+ branchName;?>
+
multiple):?>
execution):?>
- executionName;?>
+
+ executionName;?>
+ executionDeleted) $executionName = "$executionName";?>
+
+ executionDeleted):?>
+ build->deleted;?>
+
+
@@ -97,8 +108,26 @@
- scmPath, 'http') === 0 ? html::a($build->scmPath) : $build->scmPath;?>
- filePath, 'http') === 0 ? html::a($build->filePath) : $build->filePath;?>
+
+ scmPath)
+ {
+ $colorStyle = strpos($build->scmPath, 'http') === 0 ? "style='color:#2463c7;'" : '';
+ echo " ";
+ echo "";
+ echo $colorStyle ? html::a($build->scmPath, $build->scmPath, '_blank', $colorStyle) : $build->scmPath;
+ echo '
';
+ }
+ if($build->filePath)
+ {
+ $colorStyle = strpos($build->filePath, 'http') === 0 ? "style='color:#2463c7;'" : '';
+ echo " ";
+ echo "";
+ echo $colorStyle ? html::a($build->filePath, $build->filePath, '_blank', $colorStyle) : $build->filePath;
+ echo '
';
+ }
+ ?>
+
date?>
builder);?>
build->buildOperateMenu($build, 'browse');?>
diff --git a/module/project/view/create.html.php b/module/project/view/create.html.php
index c10995d240..83beebd967 100755
--- a/module/project/view/create.html.php
+++ b/module/project/view/create.html.php
@@ -26,7 +26,7 @@
product->name);?>
-project->manageProducts);?>
+project->manageProductPlan);?>
project->budgetOverrun);?>
project->currencySymbol)?>
project->parentBudget);?>
@@ -143,64 +143,91 @@
+
+
+
- project->manageProducts;?>
-
+ project->manageProductPlan;?>
+
-
-
- branches as $branch):?>
-
- type != 'normal' and isset($branchGroups[$product->id]);?>
-
+
+
+
- execution->linkPlan;?>
-
+ project->manageProductPlan;?>
+
-
-
-
- id, array(0 => ''));?>
- branches as $branch):?>
- ''));?>
-
id . '][' . $branch . '][]', $plans, '', "class='form-control chosen' multiple");?>
-
-
-
-
-
-
+
+
+
+
+
+ project->branch;?>
+
+
+
+
+
+
+
+
+
+ project->addProduct, '', "onchange=addNewProduct(this);");?>
+
project->division;?>
diff --git a/module/project/view/edit.html.php b/module/project/view/edit.html.php
index b9430faf21..32d0f8f238 100755
--- a/module/project/view/edit.html.php
+++ b/module/project/view/edit.html.php
@@ -146,49 +146,45 @@
hasProduct and $this->config->vision != 'lite'):?>
+
+
- project->manageProducts;?>
-
+ project->manageProductPlan;?>
+
-
-
- type != 'normal' and isset($branchGroups[$product->id]);?>
- id] as $branchID => $branch):?>
-
-
- execution->linkPlan;?>
-
-
-
-
- id] as $branchID => $branch):?>
- id][$branchID]) ? $productPlans[$product->id][$branchID] : array();?>
-
id}][{$branchID}][]", $plans, $branches[$product->id][$branchID]->plan, "class='form-control chosen' multiple");?>
-
-
-
-
-
-
-
+
+
model == 'waterfall'):?>
division and count($linkedProducts) < 2) ? 'hide' : '';?>
diff --git a/module/project/view/view.html.php b/module/project/view/view.html.php
index 35671524bf..aabf2af686 100644
--- a/module/project/view/view.html.php
+++ b/module/project/view/view.html.php
@@ -242,6 +242,24 @@
+ hasProduct) and !empty($config->URAndSR) and $project->model !== 'kanban' and isset($lang->project->menu->storyGroup)):?>
+
+ story->common;?>
+ storyCount;?>
+ requirement->common;?>
+ requirementCount;?>
+
+
+ task->common;?>
+ taskCount;?>
+ bug->common;?>
+ bugCount;?>
+
+
+ project->budget;?>
+ budget;?>
+
+
story->common;?>
storyCount;?>
@@ -254,6 +272,7 @@
project->budget;?>
budget;?>
+
diff --git a/module/projectrelease/control.php b/module/projectrelease/control.php
index f06687f7b4..279122c073 100644
--- a/module/projectrelease/control.php
+++ b/module/projectrelease/control.php
@@ -87,6 +87,16 @@ class projectrelease extends control
$releases = $this->projectrelease->getList($projectID, $type, $orderBy, $pager);
+ $showBranch = false;
+ foreach($releases as $release)
+ {
+ if($release->productType != 'normal')
+ {
+ $showBranch = true;
+ break;
+ }
+ }
+
$this->view->title = $objectName . $this->lang->colon . $this->lang->release->browse;
$this->view->execution = $execution;
$this->view->project = $project;
@@ -97,6 +107,7 @@ class projectrelease extends control
$this->view->type = $type;
$this->view->from = $this->app->tab;
$this->view->pager = $pager;
+ $this->view->showBranch = $showBranch;
$this->display();
}
@@ -133,17 +144,15 @@ class projectrelease extends control
$this->commonAction($projectID);
/* Get the builds that can select. */
- $builds = $this->build->getBuildPairs($this->view->product->id, $this->view->branch, 'notrunk|withbranch|hasproject', $projectID, 'project', '', false);
+ $builds = $this->build->getBuildPairs($this->view->product->id, 'all', 'notrunk|withbranch|hasproject', $projectID, 'project', '', false);
$releasedBuilds = $this->projectrelease->getReleasedBuilds($projectID);
foreach($releasedBuilds as $build) unset($builds[$build]);
-
+
$this->view->title = $this->view->project->name . $this->lang->colon . $this->lang->release->create;
$this->view->projectID = $projectID;
$this->view->builds = $builds;
$this->view->lastRelease = $this->projectrelease->getLast($projectID);
$this->view->users = $this->loadModel('user')->getPairs('noclosed');
- $this->view->confirmLink = $this->lang->release->confirmLink;
- $this->view->notEmptyBuilds = $this->build->filterLinked(array_keys($builds));
$this->display();
}
@@ -249,20 +258,23 @@ class projectrelease extends control
$sort = common::appendOrder($orderBy);
if(strpos($sort, 'pri_') !== false) $sort = str_replace('pri_', 'priOrder_', $sort);
+ $sort .= ',buildID_asc';
$storyPager = new pager($type == 'story' ? $recTotal : 0, $recPerPage, $type == 'story' ? $pageID : 1);
- $stories = $this->dao->select("*, IF(`pri` = 0, {$this->config->maxPriValue}, `pri`) as priOrder")->from(TABLE_STORY)
- ->where('id')->in($release->stories)
- ->andWhere('deleted')->eq(0)
+ $stories = $this->dao->select("t1.*,t2.id as buildID, t2.name as buildName,IF(`pri` = 0, {$this->config->maxPriValue}, `pri`) as priOrder")->from(TABLE_STORY)->alias('t1')
+ ->leftJoin(TABLE_BUILD)->alias('t2')->on("FIND_IN_SET(t1.id, t2.stories)")
+ ->where('t1.id')->in($release->stories)
+ ->andWhere('t1.deleted')->eq(0)
->beginIF($type == 'story')->orderBy($sort)->fi()
->page($storyPager)
->fetchAll('id');
- $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story');
+ $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story', false);
$stages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->in(array_keys($stories))->andWhere('branch')->eq($release->branch)->fetchPairs('story', 'stage');
foreach($stages as $storyID => $stage) $stories[$storyID]->stage = $stage;
$bugPager = new pager($type == 'bug' ? $recTotal : 0, $recPerPage, $type == 'bug' ? $pageID : 1);
+ $sort = common::appendOrder($orderBy);
$bugs = $this->dao->select('*')->from(TABLE_BUG)
->where('id')->in($release->bugs)
->andWhere('deleted')->eq(0)
@@ -271,8 +283,9 @@ class projectrelease extends control
->fetchAll();
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'linkedBug');
- $leftBugPager = new pager($type == 'leftBug' ? $recTotal : 0, $recPerPage, $type == 'leftBug' ? $pageID : 1);
+ $sort = common::appendOrder($orderBy);
if($type == 'leftBug' and strpos($orderBy, 'severity_') !== false) $sort = str_replace('severity_', 'severityOrder_', $sort);
+ $leftBugPager = new pager($type == 'leftBug' ? $recTotal : 0, $recPerPage, $type == 'leftBug' ? $pageID : 1);
$leftBugs = $this->dao->select("*, IF(`severity` = 0, {$this->config->maxPriValue}, `severity`) as severityOrder")->from(TABLE_BUG)
->where('id')->in($release->leftBugs)
@@ -306,6 +319,15 @@ class projectrelease extends control
$this->view->storyPager = $storyPager;
$this->view->bugPager = $bugPager;
$this->view->leftBugPager = $leftBugPager;
+ $this->view->builds = $this->loadModel('build')->getBuildPairs($release->product, 'all', 'withbranch|hasproject', 0, 'execution', '', false);
+
+ if($this->app->getViewType() == 'json')
+ {
+ unset($this->view->storyPager);
+ unset($this->view->bugPager);
+ unset($this->view->leftBugPager);
+ }
+
$this->display();
}
@@ -806,19 +828,15 @@ class projectrelease extends control
*
* @param int $projectID
* @param int $productID
- * @param int $branch
* @access public
* @return void
*/
- public function ajaxLoadBuilds($projectID, $productID, $branch = 0)
+ public function ajaxLoadBuilds($projectID, $productID)
{
- $builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'notrunk,withbranch,hasproject', $projectID, 'project', '', false);
+ $builds = $this->loadModel('build')->getBuildPairs($productID, 'all', 'notrunk,withbranch,hasproject', $projectID, 'project', '', false);
$releasedBuilds = $this->projectrelease->getReleasedBuilds($projectID);
foreach($releasedBuilds as $build) unset($builds[$build]);
- /* Get the builds of the linked stories or bugs. */
- $notEmptyBuilds = $this->build->filterLinked(array_keys($builds));
-
- return print(html::select('build[]', $builds, '', "class='form-control chosen' multiple data-notemptybuilds='" . join(',', $notEmptyBuilds) . "'"));
+ return print(html::select('build[]', $builds, '', "class='form-control chosen' multiple"));
}
}
diff --git a/module/projectrelease/css/browse.css b/module/projectrelease/css/browse.css
index 8f2f032ebf..a8711505bd 100644
--- a/module/projectrelease/css/browse.css
+++ b/module/projectrelease/css/browse.css
@@ -2,6 +2,9 @@
#releaseList tr:hover {background-color:unset;}
td {overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
-.c-build {width: 250px;}
-td.c-build {padding-left:8px !important;}
-.c-product, .c-execution {width: 120px;}
+#releaseList .c-id{width:60px;}
+#releaseList .c-build,#releaseList .c-product{width:180px;}
+#releaseList .c-branch {width: 120px;}
+#releaseList .c-date {width: 120px;}
+#releaseList td.c-build {padding-left:8px !important;}
+#releaseList .c-execution {width: 120px;}
diff --git a/module/projectrelease/css/view.css b/module/projectrelease/css/view.css
index 5be75906d4..07a2faa2bd 100644
--- a/module/projectrelease/css/view.css
+++ b/module/projectrelease/css/view.css
@@ -9,3 +9,17 @@
#tabsNav .nav-tabs > li.active + li.pull-right {margin-right: 0;}
ol, ul {padding-left: 20px;}
.c-id {overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
+td.c-build{white-space: nowrap; overflow:hidden;}
+th.c-user,td.c-user{text-align:left}
+
+#stories th, #stories td{padding-left:16px;padding-right:16px;}
+#stories .c-build{width:160px; text-align:left;}
+#stories .c-status{width:120px;}
+#stories .c-stage{width:112px; text-align:left;}
+#stories .c-estimate{width:90px; text-align:right;}
+#bugs .c-build{width:160px; text-align:left;}
+#bugs .c-user{width:120px;}
+#bugs .c-date{width:130px;}
+#leftBugs .c-build{width:160px; text-align:left;}
+#leftBugs .c-user{width:120px;}
+#leftBugs .c-date{width:160px;}
diff --git a/module/projectrelease/js/create.js b/module/projectrelease/js/create.js
index 43c1298bac..21925ba77f 100644
--- a/module/projectrelease/js/create.js
+++ b/module/projectrelease/js/create.js
@@ -1,27 +1,3 @@
-$('#submit').click(function()
-{
- var dateFormat = new RegExp(/^\d{4}\-\d{2}\-\d{2}$/);
- var name = $('#name').val();
- var date = $('#date').val();
- var build = $('#build').val();
- var notEmptyBuild = false;
-
- $.each(build, function(index, value)
- {
- if(typeof(notEmptyBuilds[value]) != 'undefined')
- {
- notEmptyBuild = true;
- return false;
- }
- })
-
- if(name && build && notEmptyBuild && dateFormat.test(date))
- {
- var result = confirm(confirmLink) ? true : false;
- $('#sync').val(result);
- }
-});
-
/**
* Ajax load unlinked builds with project and product.
*
@@ -31,40 +7,8 @@ $('#submit').click(function()
function loadBuilds()
{
var productID = $('#product').val();
- var branch = $('#branch').length == 0 ? 0 : $('#branch').val();
- $('#buildBox').load(createLink('projectrelease', 'ajaxLoadBuilds', "projectID=" + projectID + "&productID=" + productID + "&branch=" + branch), function()
+ $('#buildBox').load(createLink('projectrelease', 'ajaxLoadBuilds', "projectID=" + projectID + "&productID=" + productID), function()
{
$('#build').attr('data-placeholder', multipleSelect).chosen();
- $.each($('#build').attr('data-notemptybuilds').split(','), function(index, value)
- {
- notEmptyBuilds[value] = value;
- });
});
}
-
-/**
- * Flush the branch when switching products.
- *
- * @param int $productID
- * @access public
- * @return void
- */
-function loadBranches(productID)
-{
- $('#branch').remove();
- $('#branch_chosen').remove();
- $.get(createLink('branch', 'ajaxGetBranches', "productID=" + productID + '&oldBranch=0¶ms=active&projectID=' + projectID), function(data)
- {
- var $product = $('#product');
- var $inputGroup = $product.closest('.input-group');
- $inputGroup.find('.input-group-addon').toggleClass('hidden', !data);
- if(data)
- {
- $inputGroup.append(data);
- $inputGroup.find('#branch').attr('onchange', 'loadBuilds()');
- $('#branch').css('width', '120px').chosen();
- }
- loadBuilds();
- $inputGroup.fixInputGroup();
- })
-}
diff --git a/module/projectrelease/model.php b/module/projectrelease/model.php
index 9311581055..1cb51eee9e 100644
--- a/module/projectrelease/model.php
+++ b/module/projectrelease/model.php
@@ -35,6 +35,10 @@ class projectreleaseModel extends model
$release->branch = trim($release->branch, ',');
$release->build = trim($release->build, ',');
+ $release->branches = array();
+ $branchIdList = explode(',', $release->branch);
+ foreach($branchIdList as $branchID) $release->branches[$branchID] = $branchID;
+
$this->loadModel('file');
$release = $this->file->replaceImgURL($release, 'desc');
$release->files = $this->file->getByObject('release', $releaseID);
@@ -55,7 +59,7 @@ class projectreleaseModel extends model
*/
public function getList($projectID, $type = 'all', $orderBy = 't1.date_desc', $pager = null)
{
- $releases = $this->dao->select('t1.*, t2.name as productName')->from(TABLE_RELEASE)->alias('t1')
+ $releases = $this->dao->select('t1.*, t2.name as productName, t2.type as productType')->from(TABLE_RELEASE)->alias('t1')
->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id')
->where('t1.deleted')->eq(0)
->andWhere("FIND_IN_SET($projectID, t1.project)")
@@ -70,7 +74,20 @@ class projectreleaseModel extends model
{
$release->project = trim($release->project, ',');
$release->branch = trim($release->branch, ',');
- $release->build = trim($release->build, ',');
+
+ $branchName = '';
+ if($release->branch != 'normal')
+ {
+ foreach(explode(',', $release->branch) as $releaseBranch)
+ {
+ $branchName .= $this->loadModel('branch')->getById($releaseBranch);
+ $branchName .= ',';
+ }
+ $branchName = trim($branchName, ',');
+ }
+ $release->branchName = empty($branchName) ? $this->lang->branch->main : $branchName;
+ $release->build = trim($release->build, ',');
+
$buildIdList = array_merge($buildIdList, explode(',', $release->build));
}
diff --git a/module/projectrelease/view/browse.html.php b/module/projectrelease/view/browse.html.php
index befa815247..013ea04061 100644
--- a/module/projectrelease/view/browse.html.php
+++ b/module/projectrelease/view/browse.html.php
@@ -53,6 +53,9 @@
projectrelease->product;?>
release->includedBuild;?>
+
+
release->branch;?>
+
release->status;?>
release->date;?>
title='productName?>'>productName?>
-
createLink($build->execution ? 'build' : 'projectbuild', 'view', "buildID=$buildID"), $build->name, '', "data-app='project' title='name;?>'");?>
+
createLink($build->execution ? 'build' : 'projectbuild', 'view', "buildID=$buildID"), $build->name, '', "data-app='project' title='{$build->name}'");?>
processStatus('release', $release);?>
+
+
class='c-branch text-center'>branchName; ?>
+
class='c-status text-center' title=''>
diff --git a/module/projectrelease/view/create.html.php b/module/projectrelease/view/create.html.php
index fa3671ae44..a06d0bb92d 100644
--- a/module/projectrelease/view/create.html.php
+++ b/module/projectrelease/view/create.html.php
@@ -12,8 +12,6 @@
?>
-
-
@@ -41,19 +39,19 @@
release->product;?>
-
-
- id, "onchange='loadBranches(this.value)' class='form-control chosen'");?>
- type != 'normal') echo html::select('branch', $branches, $branch, "onchange='loadBuilds()' class='form-control chosen control-branch'");?>
-
-
+ id, "onchange='loadBuilds()' class='form-control chosen'");?>
release->includedBuild;?>
build->placeholder->multipleSelect}'");?>
-
+
+
+
+ release->syncFromBuilds?>
+
+
release->date;?>
@@ -84,7 +82,6 @@
-
diff --git a/module/projectrelease/view/view.html.php b/module/projectrelease/view/view.html.php
index b3ef89b38b..744c2e7285 100644
--- a/module/projectrelease/view/view.html.php
+++ b/module/projectrelease/view/view.html.php
@@ -60,7 +60,7 @@
?>
id}&type=story&link=$link¶m=$param&orderBy=%s";?>
-
+
@@ -69,16 +69,17 @@
idAB);?>
+ story->title);?>
priAB);?>
- story->title);?>
- openedByAB);?>
- story->estimateAB);?>
- statusAB);?>
- story->stageAB);?>
+ statusAB);?>
+ build->linkedBuild;?>
+ openedByAB);?>
+ story->estimateAB);?>
+ story->stageAB);?>
actions?>
-
+
$story):?>
createLink('story', 'view', "storyID=$story->id&version=0¶m={$this->session->project}", '', true);?>
@@ -91,19 +92,20 @@
id);?>
- pri;?>' title='story->priList, $story->pri, $story->pri);?>'>story->priList, $story->pri, $story->pri);?>
parent > 0) echo "{$lang->story->childrenAB} ";
echo html::a($storyLink,$story->title, '', "class='preview'");
?>
- openedBy);?>
- estimate . $config->hourUnit;?>
+ pri;?>' title='story->priList, $story->pri, $story->pri);?>'>story->priList, $story->pri, $story->pri);?>
processStatus('story', $story);?>
- story->stageList[$story->stage];?>
+ buildName?>
+ openedBy);?>
+ estimate . $config->hourUnit;?>
+ story->stageList[$story->stage];?>
idAB);?>
- bug->title);?>
- bug->status);?>
+ bug->title);?>
+ bug->status);?>
+ bug->resolvedBuild;?>
openedByAB);?>
- bug->openedDateAB);?>
- bug->resolvedByAB);?>
- bug->resolvedDateAB);?>
- actions;?>
+ bug->openedDateAB);?>
+ bug->resolvedBy);?>
+ bug->resolvedDateAB);?>
+ actions;?>
@@ -191,10 +194,12 @@
processStatus('bug', $bug);?>
- openedBy);?>
- openedDate) ? '' : substr($bug->openedDate, 5, 11);?>
- resolvedBy);?>
- resolvedDate) ? '' : substr($bug->resolvedDate, 5, 11);?>
+ resolvedBuild, '');?>
+
+ openedBy);?>
+ openedDate) ? '' : substr($bug->openedDate, 5, 11);?>
+ resolvedBy);?>
+ resolvedDate) ? '' : substr($bug->resolvedDate, 5, 11);?>
idAB);?>
+ bug->title);?>
bug->severity;?>>bug->severityAB);?>
- bug->title);?>
- bug->status);?>
- openedByAB);?>
- bug->openedDateAB);?>
- actions;?>
+ bug->status);?>
+ bug->openedBuild;?>
+ openedByAB);?>
+ bug->openedDateAB);?>
+ actions;?>
id);?>
+ title, '', "class='preview'");?>
' title='bug->severityList, $bug->severity);?>' data-severity='severity;?>'>bug->severityList, $bug->severity, $bug->severity);?>
@@ -293,10 +300,14 @@
- title, '', "class='preview'");?>
processStatus('bug', $bug);?>
- openedBy);?>
- openedDate?>
+ openedBuild) as $buildID) $openedBuildName .= zget($builds, $buildID, '') . ' ';
+ ?>
+
+ openedBy);?>
+ openedDate?>
release->product;?>
productName;?>
- productType != 'normal'):?>
-
- product->branch, $lang->product->branchName[$product->type]);?>
-
-
-
release->name;?>
@@ -362,6 +367,20 @@
?>
+ type != 'normal'):?>
+
+ release->branch;?>
+
+ branches as $branchID)
+ {
+ echo zget($branches, $branchID, '');
+ if($branchID != end($release->branches)) echo ', ';
+ }
+ ?>
+
+
+
release->status;?>
processStatus('release', $release);?>
diff --git a/module/release/control.php b/module/release/control.php
index 360cad5302..f0fc18329a 100644
--- a/module/release/control.php
+++ b/module/release/control.php
@@ -50,12 +50,23 @@ class release extends control
$uri = $this->app->getURI(true);
$this->session->set('releaseList', $uri, 'product');
$this->session->set('buildList', $uri);
+ $releases = $this->release->getList($productID, $branch, $type, $orderBy, $pager);
+ $showBranch = false;
+ foreach($releases as $release)
+ {
+ if($release->productType != 'normal')
+ {
+ $showBranch = true;
+ break;
+ }
+ }
$this->view->title = $this->view->product->name . $this->lang->colon . $this->lang->release->browse;
$this->view->position[] = $this->lang->release->browse;
- $this->view->releases = $this->release->getList($productID, $branch, $type, $orderBy, $pager);
+ $this->view->releases = $releases;
$this->view->type = $type;
$this->view->pager = $pager;
+ $this->view->showBranch = $showBranch;
$this->display();
}
@@ -96,7 +107,6 @@ class release extends control
$this->view->builds = $builds;
$this->view->users = $this->loadModel('user')->getPairs('noclosed');
$this->view->lastRelease = $this->release->getLast($productID, $branch);
- $this->view->notEmptyBuilds = $this->build->filterLinked(array_keys($builds));
$this->display();
}
@@ -184,20 +194,23 @@ class release extends control
$sort = common::appendOrder($orderBy);
if(strpos($sort, 'pri_') !== false) $sort = str_replace('pri_', 'priOrder_', $sort);
+ $sort .= ',buildID_asc';
$storyPager = new pager($type == 'story' ? $recTotal : 0, $recPerPage, $type == 'story' ? $pageID : 1);
- $stories = $this->dao->select("*, IF(`pri` = 0, {$this->config->maxPriValue}, `pri`) as priOrder")->from(TABLE_STORY)
- ->where('id')->in($release->stories)
- ->andWhere('deleted')->eq(0)
+ $stories = $this->dao->select("t1.*,t2.id as buildID, t2.name as buildName, IF(t1.`pri` = 0, {$this->config->maxPriValue}, t1.`pri`) as priOrder")->from(TABLE_STORY)->alias('t1')
+ ->leftJoin(TABLE_BUILD)->alias('t2')->on("FIND_IN_SET(t1.id, t2.stories)")
+ ->where('t1.id')->in($release->stories)
+ ->andWhere('t1.deleted')->eq(0)
->beginIF($type == 'story')->orderBy($sort)->fi()
->page($storyPager)
->fetchAll('id');
- $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story');
+ $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story', false);
$stages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->in(array_keys($stories))->andWhere('branch')->eq($release->branch)->fetchPairs('story', 'stage');
foreach($stages as $storyID => $stage) $stories[$storyID]->stage = $stage;
$bugPager = new pager($type == 'bug' ? $recTotal : 0, $recPerPage, $type == 'bug' ? $pageID : 1);
+ $sort = common::appendOrder($orderBy);
$bugs = $this->dao->select('*')->from(TABLE_BUG)
->where('id')->in($release->bugs)
->andWhere('deleted')->eq(0)
@@ -206,8 +219,9 @@ class release extends control
->fetchAll();
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'linkedBug');
- $leftBugPager = new pager($type == 'leftBug' ? $recTotal : 0, $recPerPage, $type == 'leftBug' ? $pageID : 1);
+ $sort = common::appendOrder($orderBy);
if($type == 'leftBug' and strpos($orderBy, 'severity_') !== false) $sort = str_replace('severity_', 'severityOrder_', $sort);
+ $leftBugPager = new pager($type == 'leftBug' ? $recTotal : 0, $recPerPage, $type == 'leftBug' ? $pageID : 1);
$leftBugs = $this->dao->select("*, IF(`severity` = 0, {$this->config->maxPriValue}, `severity`) as severityOrder")->from(TABLE_BUG)
->where('id')->in($release->leftBugs)
@@ -239,6 +253,7 @@ class release extends control
$this->view->storyPager = $storyPager;
$this->view->bugPager = $bugPager;
$this->view->leftBugPager = $leftBugPager;
+ $this->view->builds = $this->loadModel('build')->getBuildPairs($release->product, 'all', 'withbranch|hasproject', 0, 'execution', '', false);
if($this->app->getViewType() == 'json')
{
@@ -299,7 +314,7 @@ class release extends control
$release = $this->dao->select('*')->from(TABLE_RELEASE)->where('id')->eq((int)$releaseID)->fetch();
$build = $this->dao->select('*')->from(TABLE_BUILD)->where('id')->eq((int)$release->build)->fetch();
- $this->loadModel('build')->delete(TABLE_BUILD, $release->shadow);
+ $this->dao->update(TABLE_BUILD)->set('deleted')->eq(1)->where('id')->eq($release->shadow)->exec();
foreach($builds as $build)
{
if(empty($build->execution) and $build->createdDate == $release->createdDate) $this->build->delete(TABLE_BUILD, $build->id);
diff --git a/module/release/css/browse.css b/module/release/css/browse.css
index dd6d856b51..19a47c17a4 100644
--- a/module/release/css/browse.css
+++ b/module/release/css/browse.css
@@ -3,6 +3,8 @@ td {overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
#releaseList tbody>tr:hover{background-color: unset!important;}
.main-table tbody>tr>td:first-child{padding: 2px 8px!important;}
-.c-id {width: 60px}
-.c-branch {width: 100px}
-.c-project, c-build {width: 200px;}
+#releaseList th, #releaseList td{padding-left:16px !important;padding-right:16px !important;}
+#releaseList .c-id {width: 60px}
+#releaseList .c-branch {width: 100px}
+#releaseList .c-date {width: 160px}
+#releaseList .c-project, #releaseList .c-build {width: 180px;}
diff --git a/module/release/css/view.css b/module/release/css/view.css
index c750db6903..0495dac43c 100644
--- a/module/release/css/view.css
+++ b/module/release/css/view.css
@@ -10,3 +10,17 @@
ol, ul {padding-left: 20px;}
.body-modal #mainContent {min-height: 240px;}
.c-id {width: 110px;}
+td.c-build{white-space: nowrap; overflow:hidden;}
+th.c-user,td.c-user{text-align:left}
+
+#stories th, #stories td{padding-left:16px;padding-right:16px;}
+#stories .c-build{width:160px; text-align:left;}
+#stories .c-status{width:120px;}
+#stories .c-stage{width:112px; text-align:left;}
+#stories .c-estimate{width:90px; text-align:right;}
+#bugs .c-build{width:160px; text-align:left;}
+#bugs .c-user{width:120px;}
+#bugs .c-date{width:130px;}
+#leftBugs .c-id{width:90px;}
+#leftBugs .c-build{width:160px; text-align:left;}
+#leftBugs .c-user{width:120px;}
diff --git a/module/release/js/create.js b/module/release/js/create.js
index 87a9d72468..e69de29bb2 100644
--- a/module/release/js/create.js
+++ b/module/release/js/create.js
@@ -1,23 +0,0 @@
-$('#submit').click(function()
-{
- var dateFormat = new RegExp(/^\d{4}\-\d{2}\-\d{2}$/);
- var name = $('#name').val();
- var date = $('#date').val();
- var build = $('#build').val();
- var notEmptyBuild = false;
-
- $.each(build, function(index, value)
- {
- if(typeof(notEmptyBuilds[value]) != 'undefined')
- {
- notEmptyBuild = true;
- return false;
- }
- })
-
- if(name && build && notEmptyBuild && dateFormat.test(date))
- {
- var result = confirm(confirmLink) ? true : false;
- $('#sync').val(result);
- }
-});
diff --git a/module/release/lang/de.php b/module/release/lang/de.php
index dfd0244395..0772adf5d3 100644
--- a/module/release/lang/de.php
+++ b/module/release/lang/de.php
@@ -24,7 +24,7 @@ $lang->release->batchUnlinkStory = "Mehrere Storys entfernen";
$lang->release->batchUnlinkBug = "Mehrere Bugs entfernen";
$lang->release->confirmDelete = "Möchten Sie dieses Releas löschen?";
-$lang->release->confirmLink = "Whether to link the stories completed in the version and the bugs solved to the release?";
+$lang->release->syncFromBuilds = "Link the stories completed in the version and the bugs solved to the release";
$lang->release->confirmUnlinkStory = "Möchten Sie diese Story löschen?";
$lang->release->confirmUnlinkBug = "Möchten Sie diesen Bug löschen?";
$lang->release->existBuild = '『Build』『%s』 existiert bereits. Sie können den 『name』 ändern oder ein anderes 『build』 wählen.';
diff --git a/module/release/lang/en.php b/module/release/lang/en.php
index 7b6be960a0..ecdcfab2b6 100644
--- a/module/release/lang/en.php
+++ b/module/release/lang/en.php
@@ -24,7 +24,7 @@ $lang->release->batchUnlinkStory = "Batch Unlink Stories";
$lang->release->batchUnlinkBug = "Batch Unlink Bugs";
$lang->release->confirmDelete = "Do you want to delete this release?";
-$lang->release->confirmLink = "Whether to link the stories completed in the version and the bugs solved to the release?";
+$lang->release->syncFromBuilds = "Link the stories completed in the version and the bugs solved to the release";
$lang->release->confirmUnlinkStory = "Do you want to remove this story?";
$lang->release->confirmUnlinkBug = "Do you want to remove this bug?";
$lang->release->existBuild = '『Build』『%s』existed. You could change『name』or choose a『build』.';
diff --git a/module/release/lang/fr.php b/module/release/lang/fr.php
index 3a7bdc1b1e..e4b46198d1 100644
--- a/module/release/lang/fr.php
+++ b/module/release/lang/fr.php
@@ -24,7 +24,7 @@ $lang->release->batchUnlinkStory = "Retirer Stories par Lot";
$lang->release->batchUnlinkBug = "Retirer Bugs par Lot";
$lang->release->confirmDelete = "Voulez-vous réellement supprimer cette release ?";
-$lang->release->confirmLink = "Whether to link the stories completed in the version and the bugs solved to the release?";
+$lang->release->syncFromBuilds = "Link the stories completed in the version and the bugs solved to the release";
$lang->release->confirmUnlinkStory = "Voulez-vous retirer cette story de la release ?";
$lang->release->confirmUnlinkBug = "Voulez-vous retirer ce bug de la release ?";
$lang->release->existBuild = '『Build』『%s』existant. Vous pouvez changer『name』ou choisir un『build』.';
diff --git a/module/release/lang/zh-cn.php b/module/release/lang/zh-cn.php
index 3e48d6e1b8..9f3af90ca8 100644
--- a/module/release/lang/zh-cn.php
+++ b/module/release/lang/zh-cn.php
@@ -24,7 +24,7 @@ $lang->release->batchUnlinkStory = "批量移除{$lang->SRCommon}";
$lang->release->batchUnlinkBug = "批量移除Bug";
$lang->release->confirmDelete = "您确认删除该发布吗?";
-$lang->release->confirmLink = "是否将版本中完成的{$lang->SRCommon}和已解决的Bug关联到发布下?";
+$lang->release->syncFromBuilds = "将版本中完成的{$lang->SRCommon}和已解决的Bug关联到发布下";
$lang->release->confirmUnlinkStory = "您确认移除该{$lang->SRCommon}吗?";
$lang->release->confirmUnlinkBug = "您确认移除该Bug吗?";
$lang->release->existBuild = '『版本』已经有『%s』这条记录了。您可以更改『发布名称』或者选择一个『版本』。';
diff --git a/module/release/model.php b/module/release/model.php
index 4b8930bcc1..2b089d5860 100644
--- a/module/release/model.php
+++ b/module/release/model.php
@@ -31,7 +31,11 @@ class releaseModel extends model
->fetch();
if(!$release) return false;
- $release->builds = $this->dao->select('id, filePath, scmPath, name, execution, project')->from(TABLE_BUILD)->where('id')->in($release->build)->fetchAll();
+ $release->builds = $this->dao->select('id, branch, filePath, scmPath, name, execution, project')->from(TABLE_BUILD)->where('id')->in($release->build)->fetchAll();
+
+ $release->branches = array();
+ $branchIdList = explode(',', trim($release->branch, ','));
+ foreach($branchIdList as $branchID) $release->branches[$branchID] = $branchID;
$this->loadModel('file');
$release = $this->file->replaceImgURL($release, 'desc');
@@ -54,7 +58,7 @@ class releaseModel extends model
*/
public function getList($productID, $branch = 'all', $type = 'all', $orderBy = 't1.date_desc', $pager = null)
{
- $releases = $this->dao->select('t1.*, t2.name as productName')->from(TABLE_RELEASE)->alias('t1')
+ $releases = $this->dao->select('t1.*, t2.name as productName, t2.type as productType')->from(TABLE_RELEASE)->alias('t1')
->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id')
->where('t1.deleted')->eq(0)
->beginIF($productID)->andWhere('t1.product')->eq((int)$productID)->fi()
@@ -80,8 +84,19 @@ class releaseModel extends model
$releaseBuilds[] = $builds[$buildID];
}
-
$release->builds = $releaseBuilds;
+
+ $branchName = '';
+ if($release->branch != 'normal')
+ {
+ foreach(explode(',', $release->branch) as $releaseBranch)
+ {
+ $branchName .= $this->loadModel('branch')->getById($releaseBranch);
+ $branchName .= ',';
+ }
+ $branchName = trim($branchName, ',');
+ }
+ $release->branchName = empty($branchName) ? $this->lang->branch->main : $branchName;
}
return $releases;
@@ -220,12 +235,17 @@ class releaseModel extends model
$linkedBuilds = array_merge($linkedBuilds, explode(',', $build->builds));
}
if($linkedBuilds) $builds += $this->dao->select('id,project,branch,builds,stories,bugs')->from(TABLE_BUILD)->where('id')->in($linkedBuilds)->fetchAll('id');
+ $branches = array();
foreach($builds as $build)
{
- $branches[$build->branch] = $build->branch;
+ foreach(explode(',', $build->branch) as $buildBranch)
+ {
+ if(!isset($branches[$buildBranch])) $branches[$buildBranch] = $buildBranch;
+ }
+
$projects[$build->project] = $build->project;
- if($this->post->sync == 'true')
+ if($this->post->sync)
{
$build->stories = trim($build->stories, ',');
$build->bugs = trim($build->bugs, ',');
@@ -233,7 +253,7 @@ class releaseModel extends model
if($build->bugs) $release->bugs .= ',' . $build->bugs;
}
}
- if($this->post->sync == 'true' and $release->bugs)
+ if($this->post->sync and $release->bugs)
{
$releaseBugs = $this->loadModel('bug')->getReleaseBugs(array_keys($builds), $release->product, $release->branch);
$release->bugs = join(',', array_intersect(explode(',', $release->bugs), array_keys($releaseBugs)));
@@ -320,10 +340,14 @@ class releaseModel extends model
/* update release project and branch */
if($release->build)
{
- $builds = $this->dao->select('project, branch')->from(TABLE_BUILD)->where('id')->in($release->build)->fetchAll();
+ $builds = $this->dao->select('project, branch')->from(TABLE_BUILD)->where('id')->in($release->build)->fetchAll();
+ $branches = array();
foreach($builds as $build)
{
- $branches[$build->branch] = $build->branch;
+ foreach(explode(',', $build->branch) as $buildBranch)
+ {
+ if(!isset($branches[$buildBranch])) $branches[$buildBranch] = $buildBranch;
+ }
$projects[$build->project] = $build->project;
}
$release->build = ',' . trim($release->build, ',') . ',';
diff --git a/module/release/view/browse.html.php b/module/release/view/browse.html.php
index ad9b6aa7c4..293bc3d711 100644
--- a/module/release/view/browse.html.php
+++ b/module/release/view/browse.html.php
@@ -52,6 +52,9 @@
release->id;?>
release->name;?>
release->includedBuild;?>
+
+ release->branch;?>
+
release->relatedProject;?>
release->status;?>
release->date;?>
@@ -84,7 +87,7 @@
?>
-
+ '>
type != 'normal'):?>
branchName;?>
@@ -98,6 +101,11 @@
?>
+
+
+ class='c-branch text-center'>branchName; ?>
+
+
projectName;?>
processStatus('release', $release);?>
diff --git a/module/release/view/create.html.php b/module/release/view/create.html.php
index 1c41c1a01f..5bc476e931 100644
--- a/module/release/view/create.html.php
+++ b/module/release/view/create.html.php
@@ -12,8 +12,6 @@
?>
-release->confirmLink);?>
-
@@ -36,7 +34,12 @@
release->includedBuild;?>
build->placeholder->multipleSelect}'");?>
-
+
+
+
+ release->syncFromBuilds?>
+
+
release->date;?>
@@ -67,7 +70,6 @@
-
diff --git a/module/release/view/view.html.php b/module/release/view/view.html.php
index 6ec80c73bd..90355fef32 100644
--- a/module/release/view/view.html.php
+++ b/module/release/view/view.html.php
@@ -62,7 +62,7 @@
?>
id}&type=story&link=$link¶m=$param&orderBy=%s";?>
-
+
@@ -71,16 +71,17 @@
idAB);?>
+ story->title);?>
pri;?>>priAB);?>
- story->title);?>
- openedByAB);?>
- story->estimateAB);?>
- statusAB);?>
- story->stageAB);?>
+ statusAB);?>
+ build->linkedBuild;?>
+ openedByAB);?>
+ story->estimateAB);?>
+ story->stageAB);?>
actions?>
-
+
$story):?>
createLink('story', 'view', "storyID=$story->id", '', true);?>
@@ -93,19 +94,20 @@
id);?>
- pri;?>' title='story->priList, $story->pri, $story->pri);?>'>story->priList, $story->pri, $story->pri);?>
parent > 0) echo "{$lang->story->childrenAB} ";
echo html::a($storyLink,$story->title, '', "class='preview'");
?>
- openedBy);?>
- estimate . $config->hourUnit;?>
-
+ pri;?>' title='story->priList, $story->pri, $story->pri);?>'>story->priList, $story->pri, $story->pri);?>
+
processStatus('story', $story);?>
- story->stageList[$story->stage];?>
+ buildName?>
+ openedBy);?>
+ estimate . $config->hourUnit;?>
+ story->stageList[$story->stage];?>
idAB);?>
- bug->title);?>
- bug->status);?>
+ bug->title);?>
+ bug->status);?>
+ bug->resolvedBuild;?>
openedByAB);?>
- bug->openedDateAB);?>
- bug->resolvedByAB);?>
- bug->resolvedDateAB);?>
- actions;?>
+ bug->openedDateAB);?>
+ bug->resolvedBy);?>
+ bug->resolvedDateAB);?>
+ actions;?>
@@ -193,10 +196,12 @@
processStatus('bug', $bug);?>
- openedBy);?>
- openedDate, 5, 11)?>
- resolvedBy);?>
- resolvedDate) ? '' : substr($bug->resolvedDate, 5, 11);?>
+ resolvedBuild, '');?>
+
+ openedBy);?>
+ openedDate, 5, 11)?>
+ resolvedBy);?>
+ resolvedDate) ? '' : substr($bug->resolvedDate, 5, 11);?>
idAB);?>
+ bug->title);?>
bug->severity;?>>bug->severityAB);?>
- bug->title);?>
- bug->status);?>
- openedByAB);?>
- bug->openedDateAB);?>
- actions;?>
+ bug->status);?>
+ bug->openedBuild;?>
+ openedByAB);?>
+ bug->openedDateAB);?>
+ actions;?>
id);?>
+ title, '', "class='preview'");?>
' title='bug->severityList, $bug->severity);?>' data-severity='severity;?>'>bug->severityList, $bug->severity, $bug->severity);?>
@@ -295,10 +302,14 @@
- title, '', "class='preview'");?>
processStatus('bug', $bug);?>
- openedBy);?>
- openedDate?>
+ openedBuild) as $buildID) $openedBuildName .= zget($builds, $buildID, '') . ' ';
+ ?>
+
+ openedBy);?>
+ openedDate?>
release->product;?>
productName;?>
- productType != 'normal'):?>
-
- product->branch;?>
-
-
-
release->name;?>
name;?>
@@ -367,6 +372,20 @@
?>
+ type != 'normal'):?>
+
+ release->branch;?>
+
+ branches as $branchID)
+ {
+ echo zget($branches, $branchID, '');
+ if($branchID != end($release->branches)) echo ', ';
+ }
+ ?>
+
+
+
release->status;?>
processStatus('release', $release);?>
diff --git a/module/report/view/annualdata.html.php b/module/report/view/annualdata.html.php
index 35234b8d7a..68c95a9617 100644
--- a/module/report/view/annualdata.html.php
+++ b/module/report/view/annualdata.html.php
@@ -292,31 +292,39 @@ $(function()
timeline: {
show: yearsData.length && yearsData.length > 1,
axisType: 'category',
- autoPlay: true,
- loop: false,
+ autoPlay: false,
+ loop: true,
playInterval: 1000,
left: "1%",
bottom: "1%",
width: '90%',
data: yearsData,
- symbolSize: [8, 4],
+ currentIndex: yearsData.length - 1,
+ symbolSize: [5, 5],
label: {
- normal: {
- color: '#fff',
- interval: 'auto',
- fontSize: 12,
- lineHeight: 20
- },
+ fontSize: 10,
+ lineHeight: 18,
interval: 'auto',
},
+ emphasis: {
+ label: {
+ fontSize: 10,
+ },
+ },
+ progress: {
+ label: {
+ fontSize: 10,
+ },
+ },
lineStyle: {
width: 5,
},
controlStyle: {
- itemSize: 18
+ itemSize: 18,
},
},
+ tooltip: {},
series: [{
name:'radar;?>',
areaStyle:{color: 'rgb(45, 40, 33)'},
diff --git a/module/story/control.php b/module/story/control.php
index f9a5eb3177..530566b625 100755
--- a/module/story/control.php
+++ b/module/story/control.php
@@ -128,6 +128,7 @@ class story extends control
$response['result'] = 'success';
setcookie('lastStoryModule', (int)$this->post->module, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, false);
+
$storyResult = $this->story->create($objectID, $bugID, $from = isset($fromObjectIDKey) ? $fromObjectIDKey : '', $extra);
if(!$storyResult or dao::isError())
{
@@ -164,7 +165,9 @@ class story extends control
$action = $fromObjectAction;
$extra = $fromObjectID;
}
- $actionID = $this->action->create('story', $storyID, $action, '', $extra);
+ /* Create actions. */
+ $storyIds = $storyResult['ids'];
+ foreach($storyIds as $idItem) $actionID = $this->action->create('story', $idItem, $action, '', $extra);
/* Record submit review action. */
$story = $this->dao->findById((int)$storyID)->from(TABLE_STORY)->fetch();
@@ -260,7 +263,9 @@ class story extends control
}
else
{
- $response['locate'] = $this->session->storyList;
+ $sessionStoryList = $this->session->storyList;
+ if(count($_POST['branches']) > 1) $sessionStoryList = preg_replace('/branch=(\d+|[A-Za-z]+)/', 'branch=all', $this->session->storyList);
+ $response['locate'] = $sessionStoryList;
}
}
}
@@ -295,7 +300,11 @@ class story extends control
}
$users = $this->user->getPairs('pdfirst|noclosed|nodeleted');
+
+ $branchData = explode(',', $branch);
+ $branch = current($branchData);
$moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'story', 0, $branch === 'all' ? 0 : $branch);
+
if(empty($moduleOptionMenu)) return print(js::locate(helper::createLink('tree', 'browse', "productID=$productID&view=story")));
/* Init vars. */
@@ -437,7 +446,7 @@ class story extends control
$this->view->users = $users;
$this->view->moduleID = $moduleID;
$this->view->moduleOptionMenu = $moduleOptionMenu;
- $this->view->plans = str_replace('2030-01-01', $this->lang->story->undetermined, $this->loadModel('productplan')->getPairsForStory($productID, $branch, 'skipParent|unexpired|noclosed'));
+ $this->view->plans = str_replace('2030-01-01', $this->lang->story->undetermined, $this->loadModel('productplan')->getPairsForStory($productID, $branch == 0 ? '' : $branch, 'skipParent|unexpired|noclosed'));
$this->view->planID = $planID;
$this->view->source = $source;
$this->view->sourceNote = $sourceNote;
@@ -656,7 +665,10 @@ class story extends control
$branches = $product->type != 'normal' ? $this->loadModel('branch')->getPairs($productID, 'active') : array();
}
- $moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'story', 0, $branch === 'all' ? 0 : $branch);
+ $branchData = explode(',', $branch);
+ $branch = current($branchData);
+
+ $moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'story', 0, $branch === 'all' ? 0 : $branch);
$moduleOptionMenu['ditto'] = $this->lang->story->ditto;
/* Get reviewers. */
@@ -681,7 +693,7 @@ class story extends control
}
$this->view->titles = $titles;
}
- $plans = $this->loadModel('productplan')->getPairsForStory($productID, ($branch === 'all' or !in_array($branch, array_keys($branches))) ? 0 : $branch, 'skipParent|unexpired|noclosed');
+ $plans = $this->loadModel('productplan')->getPairsForStory($productID, ($branch === 'all' or empty($branch)) ? '' : $branch, 'skipParent|unexpired|noclosed');
$plans['ditto'] = $this->lang->story->ditto;
$priList = (array)$this->lang->story->priList;
@@ -931,6 +943,7 @@ class story extends control
$this->view->title = $this->lang->story->edit . "STORY" . $this->lang->colon . $this->view->story->title;
$this->view->position[] = $this->lang->story->edit;
$this->view->story = $story;
+ $this->view->twins = empty($story->twins) ? array() : $this->story->getByList($story->twins);
$this->view->stories = $stories;
$this->view->users = $users;
$this->view->product = $product;
@@ -939,6 +952,7 @@ class story extends control
$this->view->productStories = $productStories;
$this->view->branchOption = $branchOption;
$this->view->branchTagOption = $branchTagOption;
+ $this->view->branches = $product->type == 'normal' ? array() : $this->loadModel('branch')->getPairs($product->id);
$this->view->reviewers = array_keys($reviewerList);
$this->view->reviewedReviewer = $reviewedReviewer;
$this->view->lastReviewer = $this->story->getLastReviewer($story->id);
@@ -1023,6 +1037,16 @@ class story extends control
/* Get edited stories. */
$stories = $this->story->getByList($storyIdList);
+ /* Filter twins. */
+ $twins = '';
+ foreach($stories as $id => $story)
+ {
+ if(empty($story->twins)) continue;
+ $twins .= "#$id ";
+ unset($stories[$id]);
+ }
+ if(!empty($twins)) echo js::alert(sprintf($this->lang->story->batchEditTip, $twins));
+
$this->loadModel('branch');
if($productID and !$executionID)
{
@@ -1042,7 +1066,7 @@ class story extends control
$moduleList = $branchProduct ? $modulePairs : array(0 => $modulePairs);
$modules = array($productID => $moduleList);
- $plans = array($productID => $this->productplan->getBranchPlanPairs($productID, '', true));
+ $plans = array($productID => $this->productplan->getBranchPlanPairs($productID, '', 'unexpired', true));
$products = array($productID => $product);
$branchTagOption = array($productID => $branchTagOption);
}
@@ -1052,6 +1076,7 @@ class story extends control
$modules = array();
$branchTagOption = array();
$products = array();
+ $plans = array();
/* Get product id list by the stories. */
$productIdList = array();
@@ -1073,7 +1098,7 @@ class story extends control
$modulePairs = $this->tree->getOptionMenu($storyProduct->id, 'story', 0, $branches);
$modules[$storyProduct->id] = $storyProduct->type != 'normal' ? $modulePairs : array(0 => $modulePairs);
- $plans[$storyProduct->id] = $this->productplan->getBranchPlanPairs($storyProduct->id, $branches, true);
+ $plans[$storyProduct->id] = $this->productplan->getBranchPlanPairs($storyProduct->id, $branches, 'unexpired', true);
if(empty($plans[$storyProduct->id])) $plans[$storyProduct->id][0] = $plans[$storyProduct->id];
if($storyProduct->type != 'normal') $branchProduct = true;
@@ -1251,6 +1276,8 @@ class story extends control
/* Assign. */
$this->view->title = $this->lang->story->change . "STORY" . $this->lang->colon . $this->view->story->title;
+ $this->view->twins = empty($story->twins) ? array() : $this->story->getByList($story->twins);
+ $this->view->branches = $this->loadModel('branch')->getPairs($story->product);
$this->view->users = $this->user->getPairs('pofirst|nodeleted|noclosed', $this->view->story->assignedTo);
$this->view->position[] = $this->lang->story->change;
$this->view->needReview = (($this->app->user->account == $this->view->product->PO or $this->config->story->needReview == 0 or !$this->story->checkForceReview()) and empty($reviewer)) ? "checked='checked'" : "";
@@ -1421,6 +1448,7 @@ class story extends control
$this->view->position = $position;
$this->view->product = $product;
$this->view->branches = $product->type == 'normal' ? array() : $this->loadModel('branch')->getPairs($product->id);
+ $this->view->twins = !empty($story->twins) ? $this->story->getByList($story->twins) : array();
$this->view->plan = $plan;
$this->view->bugs = $bugs;
$this->view->fromBug = $fromBug;
@@ -1642,7 +1670,7 @@ class story extends control
if(!$this->post->storyIdList) return print(js::locate($this->session->storyList, 'parent'));
$storyIdList = $this->post->storyIdList;
$storyIdList = array_unique($storyIdList);
- $actions = $this->story->batchReview($storyIdList, $result, $reason);
+ $this->story->batchReview($storyIdList, $result, $reason);
if(dao::isError()) return print(js::error(dao::getError()));
if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther');
@@ -1879,8 +1907,17 @@ class story extends control
$stories = $this->story->getByList($storyIdList);
$productStoryList = array();
$productList = array();
+ $ignoreTwins = array();
+ $twinsCount = 0;
foreach($stories as $story)
{
+ if(!empty($ignoreTwins) and isset($ignoreTwins[$story->id]))
+ {
+ $twinsCount ++;
+ unset($stories[$story->id]);
+ continue;
+ }
+
if($story->parent == -1)
{
$skipStory[] = $story->id;
@@ -1895,6 +1932,11 @@ class story extends control
$storyProduct = isset($productList[$story->product]) ? $productList[$story->product] : $this->product->getByID($story->product);
$branch = $storyProduct->type == 'branch' ? ($story->branch > 0 ? $story->branch : '0') : 'all';
if(!isset($productStoryList[$story->product][$story->branch])) $productStoryList[$story->product][$story->branch] = $this->story->getProductStoryPairs($story->product, $branch, 0, 'all', 'id_desc', 0, '', $story->type);
+
+ if(!empty($story->twins))
+ {
+ foreach(explode(',', trim($story->twins, ',')) as $twinID) $ignoreTwins[$twinID] = $twinID;
+ }
}
if($this->post->comments)
@@ -1912,6 +1954,8 @@ class story extends control
$actionID = $this->action->create('story', $storyID, 'Closed', htmlSpecialString($this->post->comments[$storyID]), ucfirst($this->post->closedReasons[$storyID]) . ($this->post->duplicateStoryIDList[$storyID] ? ':' . (int)$this->post->duplicateStoryIDList[$storyID] : '') . "|$preStatus");
$this->action->logHistory($actionID, $changes);
+
+ if(!empty($stories[$storyID]->twins)) $this->story->syncTwins($storyID, $stories[$storyID]->twins, $changes, 'Closed');
}
}
@@ -1979,6 +2023,7 @@ class story extends control
$this->view->storyType = $storyType;
$this->view->reasonList = $this->lang->story->reasonList;
$this->view->productStoryList = $productStoryList;
+ $this->view->twinsCount = $twinsCount;
$this->display();
}
@@ -2258,12 +2303,27 @@ class story extends control
{
if(!empty($_POST) && isset($_POST['storyIdList']))
{
- $allChanges = $this->story->batchAssignTo();
+ $allChanges = $this->story->batchAssignTo();
if(dao::isError()) return print(js::error(dao::getError()));
+
+ $assignedTwins = array();
+ $oldStories = $this->story->getByList($this->post->storyIdList);
foreach($allChanges as $storyID => $changes)
{
$actionID = $this->action->create('story', $storyID, 'Assigned', '', $this->post->assignedTo);
$this->action->logHistory($actionID, $changes);
+
+ /* Sync twins. */
+ if(!empty($oldStories[$storyID]->twins))
+ {
+ $twins = $oldStories[$storyID]->twins;
+ foreach(explode(',', $twins) as $twinID)
+ {
+ if(in_array($twinID, $this->post->storyIdList) or isset($assignedTwins[$twinID])) $twins = str_replace(",$twinID,", ',', $twins);
+ }
+ $this->story->syncTwins($storyID, trim($twins, ','), $changes, 'Assigned');
+ foreach(explode(',', trim($twins, ',')) as $assignedID) $assignedTwins[$assignedID] = $assignedID;
+ }
}
}
if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther');
@@ -2338,7 +2398,7 @@ class story extends control
if($type == 'remove')
{
$result = $this->story->unlinkStory($storyID, $linkedStoryID);
- return print(js::reload('parent'));
+ helprt::end();
}
if($_POST)
@@ -2972,4 +3032,29 @@ class story extends control
return print(html::select('URS[]', $URS, $requirementList, "class='form-control chosen' multiple"));
}
+
+ /**
+ * AJAX: Deleted story twin.
+ *
+ * @access public
+ * @return void
+ */
+ public function ajaxRelieveTwins()
+ {
+ $twinID = !empty($_POST['twinID']) ? $_POST['twinID'] : 0;
+ $story = $this->story->getByID($twinID);
+ $twins = explode(',', trim($story->twins, ','));
+
+ if(empty($story->twins)) return $this->send(array('result' => 'fail'));
+
+ /* batchUnset twinID from twins.*/
+ $replaceSql = "UPDATE " . TABLE_STORY . " SET twins = REPLACE(twins,',$twinID,', ',') WHERE `product` = $story->product";
+ $this->dbh->exec($replaceSql);
+
+ /* Update twins to empty by twinID and if twins eq ','.*/
+ $this->dao->update(TABLE_STORY)->set('twins')->eq('')->where('id')->eq($twinID)->orWhere('twins')->eq(',')->exec();
+
+ if(!dao::isError()) $this->loadModel('action')->create('story', $twinID, 'relieved');
+ return $this->send(array('result' => 'success', 'silbingsCount' => count($twins)-1));
+ }
}
diff --git a/module/story/css/change.css b/module/story/css/change.css
index 3fec429184..c82effceff 100644
--- a/module/story/css/change.css
+++ b/module/story/css/change.css
@@ -2,4 +2,9 @@
#affectedProjects .table + h6 {border-top: 1px solid #ddd;}
.nav-tabs>li>a {height: 36px;}
.table-form>tbody>tr>th {width: 100px !important;}
-.colorpicker {right: 7px; top: 7px; opacity: 1;}
\ No newline at end of file
+.colorpicker {right: -1px; top: 7px; opacity: 1;}
+#twinsList td, #twinsTitle td {padding-left: 65px;}
+#twinsTitle td {font-weight: 700;}
+
+.title-group.required:after {display: none;}
+.title-group.required > .required:after {display: block; right: 32px; top: 5px;}
diff --git a/module/story/css/common.css b/module/story/css/common.css
index 3baf85cc15..bd99600afe 100644
--- a/module/story/css/common.css
+++ b/module/story/css/common.css
@@ -1,3 +1,4 @@
+.icon-exclamation-sign {color: #0075ff;}
.linkbox {height: 180px; overflow-y: auto;}
.tab-pane h6 {margin: 0px; padding: 10px;}
.tab-pane h6, .tab-pane table {border: 1px solid #ddd; border-top: none;}
@@ -5,7 +6,6 @@
.input-group .control-product + .chosen-container-single .chosen-single {border-radius: 2px 0 0 2px; border-right-width: 1px; padding-right: 1px;}
.input-group .control-product + .chosen-container-active .chosen-single {border-right-width: 1px; padding-right: 0;}
-.input-group .control-branch + .chosen-container-single .chosen-single {border-radius: 0 2px 2px 0; border-left-width: 0px;}
#batchCreateForm .input-group, #batchEditForm .input-group,
#batchCreateForm .input-group .form-control, #batchEditForm .input-group .form-control {position: static;}
@@ -17,3 +17,15 @@
.c-source {width: 130px}
.c-note {width: 100px}
.c-category {width: 90px}
+
+.icon-info {color: #ff9800; border-color: #ff9800;}
+.text-active {text-decoration: underline;}
+.text-cancel {text-decoration: underline;color:#838a9d;margin-left:10px;}
+.popover {border:unset;}
+.popover.bottom .arrow{border-bottom-color:#ffffff;}
+.popover-icon {float:left;}
+.popover .content {margin-left:25px;overflow: overlay;}
+.hide {display:none !important;}
+.twins > a.viewlink {padding:6px;white-space: nowrap;overflow: hidden;display: inline-flex;max-width: 70%;}
+.twins > span {max-width: 60px;white-space: nowrap;overflow: hidden;}
+.tooltip-inner {max-width: 500px;}
diff --git a/module/story/css/create.css b/module/story/css/create.css
index c9854e8d17..ad21e95319 100644
--- a/module/story/css/create.css
+++ b/module/story/css/create.css
@@ -16,3 +16,7 @@
.source .input-group-addon, .sourceNote .input-group-addon {padding-top: 9px;}
#sourceNoteBox {width: 80px;}
#moduleIdBox .input-group-addon {min-width: 77px;}
+
+.btn.btn-action, .c-actions .btn {color: grey}
+#storyNoticeBranch {font-size: 13px; color: #5e626d;}
+#storyNoticeBranch i {font-size: 14px; color: #0075ff; margin-right: 3px;}
\ No newline at end of file
diff --git a/module/story/css/edit.css b/module/story/css/edit.css
index 7b2e3a76c3..995b2c80e0 100644
--- a/module/story/css/edit.css
+++ b/module/story/css/edit.css
@@ -5,3 +5,4 @@
.needNotReviewBox {width: 130px;}
.needNotReviewBox span.input-group-addon {border: 1px solid #dcdcdc; border-left-width: 0px; width:130px;}
#moduleIdBox .chosen-container, #planIdBox .chosen-container {min-width: unset;}
+#legendTwins .form-group {margin-top: 10px;}
diff --git a/module/story/css/view.css b/module/story/css/view.css
index cec1c3f366..ed538b7734 100644
--- a/module/story/css/view.css
+++ b/module/story/css/view.css
@@ -11,6 +11,5 @@
.linkStoryTitle {white-space: nowrap; text-overflow: ellipsis; overflow: hidden;}
.linkStoryTr > th {width: 90px;}
.linkStoryTr > td {padding-left: 0px !important;}
-.removeButton {margin-left: -10px;}
.resolution {white-space: nowrap; overflow: hidden;}
.link-commit {overflow: hidden; white-space: nowrap; margin-right: 5px;}
diff --git a/module/story/js/change.js b/module/story/js/change.js
index 3d1d19c8f4..dd48d70c1f 100644
--- a/module/story/js/change.js
+++ b/module/story/js/change.js
@@ -1,5 +1,6 @@
$(function()
{
+ $('[data-toggle="tooltip"]').tooltip();
$('#needNotReview').on('change', function()
{
$('#reviewer').val($(this).is(':checked') ? '' : lastReviewer).attr('disabled', $(this).is(':checked') ? 'disabled' : null).trigger('chosen:updated');
diff --git a/module/story/js/close.js b/module/story/js/close.js
index 4a8e9ffe28..b14bff76a6 100644
--- a/module/story/js/close.js
+++ b/module/story/js/close.js
@@ -24,4 +24,6 @@ $(function()
disableEmptySearch : true,
dropWidth : 'auto'
});
+
+ $('[data-toggle="popover"]').popover();
});
diff --git a/module/story/js/common.js b/module/story/js/common.js
index 5be7092e6e..561ab1bdb0 100644
--- a/module/story/js/common.js
+++ b/module/story/js/common.js
@@ -77,11 +77,11 @@ function getStatus(method, params)
* @access public
* @return void
*/
-function loadURS()
+function loadURS(allURS)
{
var productID = $('#product').val();
var branchID = $('#branch').val();
- var moduleID = $('#module').val();
+ var moduleID = typeof(allURS) == 'undefined' ? $('#module').val() : 0;
var requirementList = $('#URS').val();
requirementList = requirementList ? requirementList.join(',') : '';
@@ -94,3 +94,60 @@ function loadURS()
$('#URS').chosen();
});
}
+
+$('.twins').mouseover(function() {
+ if(page == 'edit') return;
+ $(this).parent('ul').find('a.unlink').addClass('hide');
+ $(this).find('.unlink').removeClass('hide');
+});
+
+$('.twins').mouseenter(function() {
+ $('[data-toggle="popover"]').popover('hide');
+});
+
+$('.twins').mouseout(function() {
+ if(page == 'edit') return;
+ $(this).find('.unlink').addClass('hide');
+});
+
+if(typeof(relievedTip) != 'undefined')
+{
+ $('[data-toggle="popover"]').each(function(item) {
+ $index = $(this).attr('data-id');
+ $(this).popover({
+ placement: 'bottom',
+ html: true,
+ content: '
' + relievedTip + '
'
+ });
+ })
+}
+
+
+function relieve(index)
+{
+ $.post(relieveURL, {twinID:index}, function(data){
+ $('[data-id="' + index + '"]').popover('hide');
+
+ if(data.result == 'success')
+ {
+ if(data.silbingsCount != 0) $('[data-id="' + index + '"]').parent('li').remove();
+ if(data.silbingsCount == 0 || index == storyID)
+ {
+ $('[href="#legendTwins"]').parent('li').next('li').addClass('active');;
+ $('[href="#legendTwins"]').parent('li').remove();
+ $('#legendTwins').next('div').addClass('active');
+ $('#legendTwins').remove();
+ $('#twinsTitle').remove();
+ $('#twinsList').remove();
+ }
+ }
+ }, 'json');
+}
+
+function popoverCancel(index)
+{
+ $('[data-id="' + index + '"]').popover('hide');
+ if(page == 'edit') return;
+
+ $('[data-id="' + index + '"]').addClass('hide');
+}
diff --git a/module/story/js/create.js b/module/story/js/create.js
index 868dfd6a20..80f87bdbe7 100644
--- a/module/story/js/create.js
+++ b/module/story/js/create.js
@@ -59,7 +59,21 @@ $(function()
return false;
});
- $(document).on('change', '#module', loadURS);
+ $('#module').on('change', function(){
+ loadURS();
+ });
+
+ if($(".table-form select[id^='branches']").length == $('.switchBranch #branchBox option').length)
+ {
+ $('.table-col .icon-plus').parent().css('pointer-events', 'none')
+ $('.table-col .icon-plus').parent().addClass('disabled')
+ }
+
+ $.get(createLink('product', 'ajaxGetProductById', "productID=" + $('#product').val()), function(data)
+ {
+ $.cookie('branchSourceName', data.branchSourceName)
+ $.cookie('branchName', data.branchName)
+ }, 'json')
});
/**
@@ -108,3 +122,217 @@ function setLane(regionID)
$(window).unload(function(){
if(blockID) window.parent.refreshBlock($('#block' + blockID));
});
+
+/**
+ * Add branch box.
+ *
+ * @param obj $obj
+ * @access public
+ * @return void
+ */
+ function addBranchesBox(obj)
+ {
+ $('#storyNoticeBranch').closest('tr').removeClass('hidden');
+ if($(".table-form select[id^='branches']").length == $('.switchBranch #branchBox option').length) return false;
+
+ var selectedVal = [];
+ $(".table-form select[id^='branches']").each(function()
+ {
+ var selectedProduct = $(this).val();
+ if($.inArray(selectedProduct, selectedVal) < 0) selectedVal.push(selectedProduct);
+ });
+
+ var branch = 0;
+ $('.table-form #branches0 option').each(function(){
+ if($.inArray($(this).val(), selectedVal) < 0)
+ {
+ branch = $(this).val();
+ return false;
+ }
+ });
+
+ var item = $('#addBranchesBox').html().replace(/%i%/g, itemIndex);
+ $(obj).closest('tr').after(' ' + item + ' ');
+ $('#branches_i__chosen').remove();
+ $('#branches' + itemIndex).chosen();
+ $('#modules_i__chosen').remove();
+ $('#modules' + itemIndex).chosen();
+ $('#plans_i__chosen').remove();
+ $('#plans' + itemIndex).chosen();
+ $('.addBranchesBox' + itemIndex + ' #planIdBox').css('flex', '0 0 ' + gap + 'px');
+
+ $.ajaxSettings.async = false;
+ loadBranchForTwins($('#product').val(), branch, itemIndex)
+ loadModuleForTwins($('#product').val(), branch, itemIndex)
+ loadPlanForTwins($('#product').val(), branch, itemIndex)
+ $.ajaxSettings.async = true;
+ $('.addBranchesBox' + itemIndex + ' #branchBox .input-group .input-group-addon').html($.cookie('branchSourceName'))
+
+ disableSelectedBranches();
+
+ if($(".table-form select[id^='branches']").length == $('.switchBranch #branchBox option').length)
+ {
+ $('.table-col .icon-plus').parent().css('pointer-events', 'none')
+ $('.table-col .icon-plus').parent().addClass('disabled')
+ }
+
+ itemIndex ++;
+ }
+
+ /**
+ * Delete branch box.
+ *
+ * @param obj $obj
+ * @access public
+ * @return void
+ */
+ function deleteBranchesBox(obj)
+ {
+ $(obj).closest('tr').remove();
+
+ disableSelectedBranches();
+
+ $('.icon-plus').parent().css('pointer-events', 'auto')
+ $('.icon-plus').parent().removeClass('disabled')
+ if($('select[name^="branches"]').length == 2) $('#storyNoticeBranch').closest('tr').addClass('hidden');
+ }
+
+ /**
+ * Make the selected branch non clickable.
+ *
+ * @return void
+ */
+function disableSelectedBranches()
+{
+ $(".table-form select[id^='branches'] option[disabled='disabled']").removeAttr('disabled');
+
+ var selectedVal = [];
+ $(".table-form select[id^='branches']").each(function()
+ {
+ var selectedBranch = $(this).val();
+ if($.inArray(selectedBranch, selectedVal) < 0) selectedVal.push(selectedBranch);
+ })
+
+ $(".table-form select[id^='branches']").each(function()
+ {
+ var selectedBranch = $(this).val();
+ $(this).find('option').each(function()
+ {
+ var optionVal = $(this).attr('value');
+ if(optionVal != selectedBranch && $.inArray(optionVal, selectedVal) >= 0) $(this).attr('disabled', 'disabled');
+ })
+ })
+
+ $(".table-form select[id^=branches]").trigger('chosen:updated');
+}
+
+ /**
+ * Load branch for multi branch or multi platform.
+ *
+ * @param int $branch
+ * @param int $branchIndex
+ * @access public
+ * @return void
+ */
+function loadBranchRelation(branch, branchIndex)
+{
+ var productID = $('#product').val();
+ if(typeof(branch) == 'undefined') branch = 0;
+
+ $.ajaxSettings.async = false;
+ loadModuleForTwins(productID, branch, branchIndex)
+ loadPlanForTwins(productID, branch, branchIndex)
+ $.ajaxSettings.async = true;
+
+ disableSelectedBranches()
+}
+
+/**
+ * Load branch for twins.
+ *
+ * @paran int $procutID
+ * @param int $branch
+ * @param int $branchIndex
+ * @access public
+ * @return void
+ */
+function loadBranchForTwins(productID, branch, branchIndex)
+{
+ var isTwins = storyType == 'story' ? 'yes' : 'no';
+ $.get(createLink('branch', 'ajaxGetBranches', "productID=" + productID + "&oldBranch=" + branch + "¶m=active&projectID=" + executionID + "&withMainBranch=1&isTwins=" + isTwins + "&fieldID=" + branchIndex), function(data)
+ {
+ if(data)
+ {
+ /* reload branch */
+ $('#branches' + branchIndex).replaceWith(data);
+ $('#branches' + branchIndex + "_chosen").remove();
+ $('#branches' + branchIndex).next('.picker').remove();
+ $('#branches' + branchIndex).chosen();
+ }
+ })
+}
+
+/**
+ * Load module for twins.
+ *
+ * @paran int $procutID
+ * @param int $branch
+ * @param int $branchIndex
+ * @access public
+ * @return void
+ */
+function loadModuleForTwins(productID, branch, branchIndex)
+{
+ /* Load module */
+ var currentModule = 0;
+ var moduleLink = createLink('tree', 'ajaxGetOptionMenu', 'productID=' + productID + '&viewtype=story&branch=' + branch + '&rootModuleID=0&returnType=html&fieldID=' + branchIndex + '&needManage=false&extra=¤tModuleID=' + currentModule);
+ if(branchIndex > 0)
+ {
+ var $moduleIDBox = $('.addBranchesBox'+ branchIndex +' #moduleIdBox');
+ }
+ else
+ {
+ var $moduleIDBox = $('.switchBranch #moduleIdBox');
+ }
+ $moduleIDBox.load(moduleLink, function()
+ {
+ $moduleIDBox.find('#modules' + branchIndex).chosen();
+ if(branchIndex == 0)
+ {
+ $('.switchBranch #moduleIdBox > span:first-child').remove()
+ }
+ $moduleIDBox.prepend("" + storyModule + " " );
+
+ $moduleIDBox.fixInputGroup();
+ });
+}
+
+/**
+ * Load plan for twins.
+ *
+ * @paran int $procutID
+ * @param int $branch
+ * @param int $branchIndex
+ * @access public
+ * @return void
+ */
+function loadPlanForTwins(productID, branch, branchIndex)
+{
+ /* Load plan */
+ if(branch == '0') branch = '';
+ planLink = createLink('product', 'ajaxGetPlans', 'productID=' + productID + '&branch=' + branch + '&planID=0&fieldID=' + branchIndex + '&needCreate=false&expired=unexpired¶m=skipParent|forStory,' + config.currentMethod);
+ if(branchIndex > 0)
+ {
+ var $planIdBox = $('.addBranchesBox'+ branchIndex +' #planIdBox');
+ }
+ else
+ {
+ var $planIdBox = $('#planIdBox');
+ }
+ $planIdBox.load(planLink, function()
+ {
+ $planIdBox.find('#plans' + branchIndex).chosen();
+ $planIdBox.prepend("" + storyPlan + " ");
+ $planIdBox.fixInputGroup();
+ });
+}
diff --git a/module/story/js/edit.js b/module/story/js/edit.js
index 8358c2009e..f1f75fb8dd 100644
--- a/module/story/js/edit.js
+++ b/module/story/js/edit.js
@@ -1,5 +1,6 @@
$(function()
{
+ $('[data-toggle="tooltip"]').tooltip();
$('.main-side #branch').closest('td').find('#product_chosen .chosen-single').css('width', '153px');
if(storyStatus == 'reviewing')
@@ -89,4 +90,9 @@ $(function()
maxAutoDropWidth : document.body.scrollWidth + document.getElementById('duplicateStory').offsetWidth - document.getElementById('duplicateStoryBox').getBoundingClientRect().right
});
}
+
+ $('#planIdBox').click(function()
+ {
+ $('#planIdBox .chosen-container').find('div').css('width', '280px')
+ });
})
diff --git a/module/story/js/view.js b/module/story/js/view.js
index 9a215855b9..94a0ce9abb 100644
--- a/module/story/js/view.js
+++ b/module/story/js/view.js
@@ -9,17 +9,19 @@ $(function()
/* Add for task #5385. */
if(config.onlybody == 'yes') $('.main-actions').css('width', '100%')
- $('#unlinkStory').click(function()
+ $('.legendStories').mouseover(function()
{
- if($('.removeButton').hasClass('hide'))
- {
- $('.removeButton').removeClass('hide');
- $(this).text(cancel);
- }
- else
- {
- $('.removeButton').addClass('hide');
- $(this).text(unlink);
- }
+ $(this).parent('ul').find('a.removeButton').addClass('hide');
+ $(this).find('.removeButton').removeClass('hide');
+ });
+
+ $('.legendStories').mouseout(function()
+ {
+ $(this).find('.removeButton').addClass('hide');
+ });
+
+ $('#legendStories').find('.removeButton').click(function()
+ {
+ $(this).closest('li').remove();
})
});
diff --git a/module/story/lang/de.php b/module/story/lang/de.php
index 95cd26c1ff..dca5206367 100644
--- a/module/story/lang/de.php
+++ b/module/story/lang/de.php
@@ -12,7 +12,7 @@
global $config;
$lang->story->create = "Story hinzufügen";
-$lang->story->requirement = zget($lang, 'URCommon', "Requirement");
+$lang->story->requirement = 'UR';
$lang->story->story = zget($lang, 'SRCommon', "Story");
$lang->story->createStory = 'Create ' . $lang->story->story;
$lang->story->createRequirement = 'Create ' . $lang->story->requirement;
@@ -76,6 +76,11 @@ $lang->story->undetermined = 'undetermined';
$lang->story->order = 'Order';
$lang->story->saveDraft = 'Save as draft';
$lang->story->doNotSubmit = 'Do Not Submit';
+$lang->story->currentBranch = 'Current Branch';
+$lang->story->twins = 'Twins story';
+$lang->story->relieved = 'Relieved';
+$lang->story->relievedTwins = 'Relieved Twins';
+$lang->story->loadAllStories = 'Load all stories';
$lang->story->editAction = "Edit {$lang->SRCommon}";
$lang->story->changeAction = "Change Story";
@@ -96,6 +101,15 @@ $lang->story->storyRound = '%s time estimation';
$lang->story->float = "『%s』should be positive number, decimals included.";
$lang->story->saveDraftSuccess = 'Save as draft succeeded.';
+$lang->story->changeSyncTip = "The modification of this story will be synchronized to the following twin requirements";
+$lang->story->syncTip = "The twin story are synchronized except for product, branch, module, plan, and stage. After the twin relationship is dissolved, they are no longer synchronized.";
+$lang->story->relievedTip = "The twin relationship cannot be restored after dissolving, the content of the demand is no longer synchronized, whether to dissolving?";
+$lang->story->assignSyncTip = "Both twin stories modify the assignor synchronously";
+$lang->story->closeSyncTip = "Twin stories are closed synchronously";
+$lang->story->activateSyncTip = "Twin stories are activated synchronously";
+$lang->story->relievedTwinsTip = "After product adjustment, the twin relationship of this story will be automatically removed, and the story will no longer be synchronized. Do you want to save?";
+$lang->story->batchEditTip = "{$lang->SRCommon} %sis twin stories, and this operation has been filtered.";
+
$lang->story->id = 'ID';
$lang->story->parent = 'Parent';
$lang->story->product = $lang->productCommon;
@@ -133,6 +147,7 @@ $lang->story->assignedTo = 'Zuständiger';
$lang->story->assignedToAB = 'Assign';
$lang->story->assignedDate = 'Zugewisen am';
$lang->story->lastEditedBy = 'Letzte Bearbeitung';
+$lang->story->lastEditedByAB = 'Letzte Bearbeitung';
$lang->story->lastEditedDate = 'Bearbeitet am';
$lang->story->closedBy = 'Geschlossen von';
$lang->story->closedDate = 'Geschlossen am';
@@ -284,6 +299,7 @@ $lang->story->checkAffection = 'Impact';
$lang->story->affectedProjects = "{$lang->project->common}s/{$lang->execution->common}s";
$lang->story->affectedBugs = 'Bug';
$lang->story->affectedCases = 'Fall';
+$lang->story->affectedTwins = 'Zwillinge';
$lang->story->specTemplate = "Als ein < type of user >, möchte ich < some goal > dass < some reason >.";
$lang->story->needNotReview = 'Keine Prüfung';
@@ -309,6 +325,7 @@ $lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist';
$lang->story->confirmRecallChange = "After undo the change, the story content will revert to the version before the change. Are you sure you want to undo?";
$lang->story->confirmRecallReview = "Are you sure you want to withdraw the review?";
$lang->story->noStoryToTask = "Only the activated {$lang->SRCommon} can be converted into a task!";
+$lang->story->ignoreClosedStory = "{$lang->SRCommon} %s status is closed, and the operation has been filtered.";
$lang->story->form = new stdclass();
$lang->story->form->area = 'Story Bereich';
@@ -344,6 +361,7 @@ $lang->story->action->subdividestory = array('main' => '$date, aufgeteilt
$lang->story->action->unlinkrelatedstory = array('main' => '$date, Verknüpfung aufgelöst durch $actor von Story $extra .');
$lang->story->action->unlinkchildstory = array('main' => '$date, Verknüpfung aufgelöst durch $actor Story $extra .');
$lang->story->action->recalledchange = array('main' => '$date, Undo changes by \$actor .');
+$lang->story->action->synctwins = array('main' => "\$date, the system judges that this story is adjusted synchronously due to the \$operate of twin story \$extra .", 'operate' => 'operateList');
/* Statistical statement. */
$lang->story->report = new stdclass();
@@ -432,6 +450,7 @@ $lang->story->chosen->reviewedBy = 'Prüfer wählen';
$lang->story->notice = new stdClass();
$lang->story->notice->closed = 'Die ausgewählten Storys wurden bereits geschlossen!';
$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.';
+$lang->story->notice->changePlan = 'The plan can be changed to only one item.';
$lang->story->convertToTask = new stdClass();
$lang->story->convertToTask->fieldList = array();
@@ -460,15 +479,27 @@ $lang->story->recallTip = array();
$lang->story->recallTip['actived'] = 'The Story has not initiated a review process and no undo action is required.';
$lang->story->subDivideTip = array();
-$lang->story->subDivideTip['subStory'] = 'The Sub-stories cannot be subdivided.';
-$lang->story->subDivideTip['notWait'] = 'The Story has been %s and cannot be subdivided.';
-$lang->story->subDivideTip['notActive'] = 'The Story is not active and cannot be subdivided.';
+$lang->story->subDivideTip['subStory'] = 'The Sub-stories cannot be subdivided.';
+$lang->story->subDivideTip['notWait'] = 'The Story has been %s and cannot be subdivided.';
+$lang->story->subDivideTip['notActive'] = 'The Story is not active and cannot be subdivided.';
+$lang->story->subDivideTip['twinsSplit'] = 'The Twins Story cannot be subdivided.';
$lang->story->featureBar['browse']['all'] = $lang->all;
$lang->story->featureBar['browse']['unclosed'] = $lang->story->unclosed;
$lang->story->featureBar['browse']['draft'] = $lang->story->statusList['draft'];
$lang->story->featureBar['browse']['reviewing'] = $lang->story->statusList['reviewing'];
+$lang->story->operateList = array();
+$lang->story->operateList['assigned'] = 'assigned';
+$lang->story->operateList['closed'] = 'closed';
+$lang->story->operateList['activated'] = 'activated';
+$lang->story->operateList['changed'] = 'changed';
+$lang->story->operateList['reviewed'] = 'reviewed';
+$lang->story->operateList['edited'] = 'edited';
+$lang->story->operateList['submitreview'] = 'submit review';
+$lang->story->operateList['recalledchange'] = 'recalled change';
+$lang->story->operateList['recalled'] = 'recalled review';
+
$lang->requirement->common = $lang->URCommon;
$lang->requirement->create = 'Create Requirement';
$lang->requirement->batchCreate = "Batch Create";
@@ -493,3 +524,11 @@ $lang->requirement->batchAssignTo = "Batch Assign";
$lang->requirement->batchChangeModule = "Batch Change Modules";
$lang->requirement->submitReview = $lang->story->submitReview;
$lang->requirement->linkStory = 'Link Story';
+
+$lang->story->addBranch = 'Add %s';
+$lang->story->deleteBranch = 'Delete %s';
+$lang->story->notice->branch = 'Each branch will establish a requirement. The requirements are twins. The twins requirements are synchronized except for the product, branch, module, plan, and stage fields. You can manually remove the twins relationship later';
+
+$lang->story->relievedTwinsRelation = 'Relieved twins relationship';
+$lang->story->relievedTwinsRelationTips = 'After the twins relationship is terminated, it cannot be restored and the shutdown of the requirement is no longer synchronized.';
+$lang->story->changeRelievedTwinsTips = 'After the twins relationship is terminated, the twin stories are no longer synchronized.';
diff --git a/module/story/lang/en.php b/module/story/lang/en.php
index 141a56b403..9fed883c8a 100644
--- a/module/story/lang/en.php
+++ b/module/story/lang/en.php
@@ -12,7 +12,7 @@
global $config;
$lang->story->create = "Create Story";
-$lang->story->requirement = zget($lang, 'URCommon', "Requirement");
+$lang->story->requirement = 'UR';
$lang->story->story = zget($lang, 'SRCommon', "Story");
$lang->story->createStory = 'Create ' . $lang->story->story;
$lang->story->createRequirement = 'Create ' . $lang->story->requirement;
@@ -76,6 +76,11 @@ $lang->story->undetermined = 'undetermined';
$lang->story->order = 'Order';
$lang->story->saveDraft = 'Save as draft';
$lang->story->doNotSubmit = 'Do Not Submit';
+$lang->story->currentBranch = 'Current Branch';
+$lang->story->twins = 'Twins story';
+$lang->story->relieved = 'Relieved';
+$lang->story->relievedTwins = 'Relieved Twins';
+$lang->story->loadAllStories = 'Load all stories';
$lang->story->editAction = "Edit {$lang->SRCommon}";
$lang->story->changeAction = "Change {$lang->SRCommon}";
@@ -96,6 +101,15 @@ $lang->story->storyRound = '%s time estimation';
$lang->story->float = "『%s』should have positive number, or decimals.";
$lang->story->saveDraftSuccess = 'Save as draft succeeded.';
+$lang->story->changeSyncTip = "The modification of this story will be synchronized to the following twin requirements";
+$lang->story->syncTip = "The twin story are synchronized except for product, branch, module, plan, and stage. After the twin relationship is dissolved, they are no longer synchronized.";
+$lang->story->relievedTip = "The twin relationship cannot be restored after dissolving, the content of the demand is no longer synchronized, whether to dissolving?";
+$lang->story->assignSyncTip = "Both twin stories modify the assignor synchronously";
+$lang->story->closeSyncTip = "Twin stories are closed synchronously";
+$lang->story->activateSyncTip = "Twin stories are activated synchronously";
+$lang->story->relievedTwinsTip = "After product adjustment, the twin relationship of this story will be automatically removed, and the story will no longer be synchronized. Do you want to save?";
+$lang->story->batchEditTip = "{$lang->SRCommon} %sis twin stories, and this operation has been filtered.";
+
$lang->story->id = 'ID';
$lang->story->parent = 'Parent';
$lang->story->product = $lang->productCommon;
@@ -133,6 +147,7 @@ $lang->story->assignedTo = 'AssignTo';
$lang->story->assignedToAB = 'Assign';
$lang->story->assignedDate = 'AssignedDate';
$lang->story->lastEditedBy = 'EditedBy';
+$lang->story->lastEditedByAB = 'LasteditedBy';
$lang->story->lastEditedDate = 'EditedDate';
$lang->story->closedBy = 'ClosedBy';
$lang->story->closedDate = 'ClosedDate';
@@ -284,6 +299,7 @@ $lang->story->checkAffection = 'Influence';
$lang->story->affectedProjects = "{$lang->project->common}s/{$lang->execution->common}s";
$lang->story->affectedBugs = 'Bugs';
$lang->story->affectedCases = 'Cases';
+$lang->story->affectedTwins = 'Twins';
$lang->story->specTemplate = "As a < type of user >, I want < some goal > so that < some reason >.";
$lang->story->needNotReview = 'No Review';
@@ -309,6 +325,7 @@ $lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist';
$lang->story->confirmRecallChange = "After undo the change, the story content will revert to the version before the change. Are you sure you want to undo?";
$lang->story->confirmRecallReview = "Are you sure you want to withdraw the review?";
$lang->story->noStoryToTask = "Only the activated {$lang->SRCommon} can be converted into a task!";
+$lang->story->ignoreClosedStory = "{$lang->SRCommon} %s status is closed, and the operation has been filtered.";
$lang->story->form = new stdclass();
$lang->story->form->area = 'Scope';
@@ -344,6 +361,7 @@ $lang->story->action->subdividestory = array('main' => '$date, decomposed
$lang->story->action->unlinkrelatedstory = array('main' => '$date, unlinked by $actor from Story $extra .');
$lang->story->action->unlinkchildstory = array('main' => '$date, unlinked by $actor Decomposed Story $extra .');
$lang->story->action->recalledchange = array('main' => '$date, Undo changes by \$actor .');
+$lang->story->action->synctwins = array('main' => "\$date, the system judges that this story is adjusted synchronously due to the \$operate of twin story \$extra .", 'operate' => 'operateList');
/* Statistical statement. */
$lang->story->report = new stdclass();
@@ -432,6 +450,7 @@ $lang->story->chosen->reviewedBy = 'Choose ReviewedBy';
$lang->story->notice = new stdClass();
$lang->story->notice->closed = 'Story that you select is closed!';
$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.';
+$lang->story->notice->changePlan = 'The plan can be changed to only one item.';
$lang->story->convertToTask = new stdClass();
$lang->story->convertToTask->fieldList = array();
@@ -460,15 +479,27 @@ $lang->story->recallTip = array();
$lang->story->recallTip['actived'] = 'The Story has not initiated a review process and no undo action is required.';
$lang->story->subDivideTip = array();
-$lang->story->subDivideTip['subStory'] = 'The Sub-stories cannot be subdivided.';
-$lang->story->subDivideTip['notWait'] = 'The Story has been %s and cannot be subdivided.';
-$lang->story->subDivideTip['notActive'] = 'The Story is not active and cannot be subdivided.';
+$lang->story->subDivideTip['subStory'] = 'The Sub-stories cannot be subdivided.';
+$lang->story->subDivideTip['notWait'] = 'The Story has been %s and cannot be subdivided.';
+$lang->story->subDivideTip['notActive'] = 'The Story is not active and cannot be subdivided.';
+$lang->story->subDivideTip['twinsSplit'] = 'The Twins Story cannot be subdivided.';
$lang->story->featureBar['browse']['all'] = $lang->all;
$lang->story->featureBar['browse']['unclosed'] = $lang->story->unclosed;
$lang->story->featureBar['browse']['draft'] = $lang->story->statusList['draft'];
$lang->story->featureBar['browse']['reviewing'] = $lang->story->statusList['reviewing'];
+$lang->story->operateList = array();
+$lang->story->operateList['assigned'] = 'assigned';
+$lang->story->operateList['closed'] = 'closed';
+$lang->story->operateList['activated'] = 'activated';
+$lang->story->operateList['changed'] = 'changed';
+$lang->story->operateList['reviewed'] = 'reviewed';
+$lang->story->operateList['edited'] = 'edited';
+$lang->story->operateList['submitreview'] = 'submit review';
+$lang->story->operateList['recalledchange'] = 'recalled change';
+$lang->story->operateList['recalled'] = 'recalled review';
+
$lang->requirement->common = $lang->URCommon;
$lang->requirement->create = 'Create Requirement';
$lang->requirement->batchCreate = "Batch Create";
@@ -493,3 +524,11 @@ $lang->requirement->batchAssignTo = "Batch Assign";
$lang->requirement->batchChangeModule = "Batch Change Modules";
$lang->requirement->submitReview = $lang->story->submitReview;
$lang->requirement->linkStory = 'Link Story';
+
+$lang->story->addBranch = 'Add %s';
+$lang->story->deleteBranch = 'Delete %s';
+$lang->story->notice->branch = 'Each branch will establish a requirement. The requirements are twins. The twins requirements are synchronized except for the product, branch, module, plan, and stage fields. You can manually remove the twins relationship later';
+
+$lang->story->relievedTwinsRelation = 'Relieved twins relationship';
+$lang->story->relievedTwinsRelationTips = 'After the twins relationship is terminated, it cannot be restored and the shutdown of the requirement is no longer synchronized.';
+$lang->story->changeRelievedTwinsTips = 'After the twins relationship is terminated, the twin stories are no longer synchronized.';
diff --git a/module/story/lang/fr.php b/module/story/lang/fr.php
index a3680cc69a..0201ebf13f 100644
--- a/module/story/lang/fr.php
+++ b/module/story/lang/fr.php
@@ -12,7 +12,7 @@
global $config;
$lang->story->create = "Créer Story";
-$lang->story->requirement = zget($lang, 'URCommon', "Requirement");
+$lang->story->requirement = 'UR';
$lang->story->story = zget($lang, 'SRCommon', "Story");
$lang->story->createStory = 'Create ' . $lang->story->story;
$lang->story->createRequirement = 'Create ' . $lang->story->requirement;
@@ -76,6 +76,11 @@ $lang->story->undetermined = 'undetermined';
$lang->story->order = 'Order';
$lang->story->saveDraft = 'Save as draft';
$lang->story->doNotSubmit = 'Do Not Submit';
+$lang->story->currentBranch = 'Current Branch';
+$lang->story->twins = 'Twins story';
+$lang->story->relieved = 'Relieved';
+$lang->story->relievedTwins = 'Relieved Twins';
+$lang->story->loadAllStories = 'Load all stories';
$lang->story->editAction = "Edit {$lang->SRCommon}";
$lang->story->changeAction = "Changer Story";
@@ -96,6 +101,15 @@ $lang->story->storyRound = '%s time estimation';
$lang->story->float = "『 %s 』doit avoir des nombres positifs ou décimaux.";
$lang->story->saveDraftSuccess = 'Save as draft succeeded.';
+$lang->story->changeSyncTip = "The modification of this story will be synchronized to the following twin requirements";
+$lang->story->syncTip = "The twin story are synchronized except for product, branch, module, plan, and stage. After the twin relationship is dissolved, they are no longer synchronized.";
+$lang->story->relievedTip = "The twin relationship cannot be restored after dissolving, the content of the demand is no longer synchronized, whether to dissolving?";
+$lang->story->assignSyncTip = "Both twin stories modify the assignor synchronously";
+$lang->story->closeSyncTip = "Twin stories are closed synchronously";
+$lang->story->activateSyncTip = "Twin stories are activated synchronously";
+$lang->story->relievedTwinsTip = "After product adjustment, the twin relationship of this story will be automatically removed, and the story will no longer be synchronized. Do you want to save?";
+$lang->story->batchEditTip = "{$lang->SRCommon} %sis twin stories, and this operation has been filtered.";
+
$lang->story->id = 'ID';
$lang->story->parent = 'Parent';
$lang->story->product = $lang->productCommon;
@@ -133,6 +147,7 @@ $lang->story->assignedTo = 'Assignée à';
$lang->story->assignedToAB = 'Attribuer';
$lang->story->assignedDate = 'Date Assignation';
$lang->story->lastEditedBy = 'Editée par';
+$lang->story->lastEditedByAB = 'Editée par';
$lang->story->lastEditedDate = 'Date Edition';
$lang->story->closedBy = 'Fermée par';
$lang->story->closedDate = 'Date Fermeture';
@@ -284,6 +299,7 @@ $lang->story->checkAffection = 'Influence';
$lang->story->affectedProjects = "{$lang->project->common}s/{$lang->execution->common}s";
$lang->story->affectedBugs = 'Bugs';
$lang->story->affectedCases = 'CasTests';
+$lang->story->affectedTwins = 'des jumeaux';
$lang->story->specTemplate = "En tant que < type utilisateur >, je veux < différents objectifs > pour < différentes raisons >.";
$lang->story->needNotReview = 'Aucune Validation Requise';
@@ -309,6 +325,7 @@ $lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist';
$lang->story->confirmRecallChange = "After undo the change, the story content will revert to the version before the change. Are you sure you want to undo?";
$lang->story->confirmRecallReview = "Are you sure you want to withdraw the review?";
$lang->story->noStoryToTask = "Only the activated {$lang->SRCommon} can be converted into a task!";
+$lang->story->ignoreClosedStory = "{$lang->SRCommon} %s status is closed, and the operation has been filtered.";
$lang->story->form = new stdclass();
$lang->story->form->area = 'Périmètre';
@@ -344,6 +361,7 @@ $lang->story->action->subdividestory = array('main' => '$date, décompos
$lang->story->action->unlinkrelatedstory = array('main' => '$date, dissociée par $actor de la Story $extra .');
$lang->story->action->unlinkchildstory = array('main' => '$date, dissociée par $actor de la sous-Story $extra .');
$lang->story->action->recalledchange = array('main' => '$date, Undo changes by \$actor .');
+$lang->story->action->synctwins = array('main' => "\$date, the system judges that this story is adjusted synchronously due to the \$operate of twin story \$extra .", 'operate' => 'operateList');
/* Statistical statement. */
$lang->story->report = new stdclass();
@@ -432,6 +450,7 @@ $lang->story->chosen->reviewedBy = 'Choisir valideur';
$lang->story->notice = new stdClass();
$lang->story->notice->closed = 'La Story que vous avez sélectionnée est malheureusement fermée !';
$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.';
+$lang->story->notice->changePlan = 'The plan can be changed to only one item.';
$lang->story->convertToTask = new stdClass();
$lang->story->convertToTask->fieldList = array();
@@ -460,15 +479,27 @@ $lang->story->recallTip = array();
$lang->story->recallTip['actived'] = "Aucun processus de révision n'a été lancé pour cette Story, et il n'est pas nécessaire de révoquer l'opération.";
$lang->story->subDivideTip = array();
-$lang->story->subDivideTip['subStory'] = 'Les sous-stories ne peuvent pas être décomposées.';
-$lang->story->subDivideTip['notWait'] = "L'exigence %s ne peut pas être subdivisée.";
-$lang->story->subDivideTip['notActive'] = "Cette Story n'est pas active et ne peut être subdivisée.";
+$lang->story->subDivideTip['subStory'] = 'Les sous-stories ne peuvent pas être décomposées.';
+$lang->story->subDivideTip['notWait'] = "L'exigence %s ne peut pas être subdivisée.";
+$lang->story->subDivideTip['notActive'] = "Cette Story n'est pas active et ne peut être subdivisée.";
+$lang->story->subDivideTip['twinsSplit'] = 'The Twins Story cannot be subdivided.';
$lang->story->featureBar['browse']['all'] = $lang->all;
$lang->story->featureBar['browse']['unclosed'] = $lang->story->unclosed;
$lang->story->featureBar['browse']['draft'] = $lang->story->statusList['draft'];
$lang->story->featureBar['browse']['reviewing'] = $lang->story->statusList['reviewing'];
+$lang->story->operateList = array();
+$lang->story->operateList['assigned'] = 'assigned';
+$lang->story->operateList['closed'] = 'closed';
+$lang->story->operateList['activated'] = 'activated';
+$lang->story->operateList['changed'] = 'changed';
+$lang->story->operateList['reviewed'] = 'reviewed';
+$lang->story->operateList['edited'] = 'edited';
+$lang->story->operateList['submitreview'] = 'submit review';
+$lang->story->operateList['recalledchange'] = 'recalled change';
+$lang->story->operateList['recalled'] = 'recalled review';
+
$lang->requirement->common = $lang->URCommon;
$lang->requirement->create = 'Create Requirement';
$lang->requirement->batchCreate = "Batch Create";
@@ -493,3 +524,11 @@ $lang->requirement->batchAssignTo = "Batch Assign";
$lang->requirement->batchChangeModule = "Batch Change Modules";
$lang->requirement->submitReview = $lang->story->submitReview;
$lang->requirement->linkStory = 'Lier Story';
+
+$lang->story->addBranch = 'Add %s';
+$lang->story->deleteBranch = 'Delete %s';
+$lang->story->notice->branch = 'Each branch will establish a requirement. The requirements are twins. The twins requirements are synchronized except for the product, branch, module, plan, and stage fields. You can manually remove the twins relationship later';
+
+$lang->story->relievedTwinsRelation = 'Relieved twins relationship';
+$lang->story->relievedTwinsRelationTips = 'After the twins relationship is terminated, it cannot be restored and the shutdown of the requirement is no longer synchronized.';
+$lang->story->changeRelievedTwinsTips = 'After the twins relationship is terminated, the twin stories are no longer synchronized.';
diff --git a/module/story/lang/vi.php b/module/story/lang/vi.php
index cf6524f8ae..980b7b8cc9 100644
--- a/module/story/lang/vi.php
+++ b/module/story/lang/vi.php
@@ -94,6 +94,14 @@ $lang->story->storyRound = '%s time estimation';
$lang->story->float = "『%s』should be positive number, decimals included.";
$lang->story->saveDraftSuccess = 'Save as draft succeeded.';
+$lang->story->changeSyncTip = "The modification of this story will be synchronized to the following twin requirements";
+$lang->story->syncTip = "The twin story are synchronized except for product, branch, module, plan, and stage. After the twin relationship is dissolved, they are no longer synchronized.";
+$lang->story->assignSyncTip = "Both twin stories modify the assignor synchronously";
+$lang->story->closeSyncTip = "Twin stories are closed synchronously";
+$lang->story->activateSyncTip = "Twin stories are activated synchronously";
+$lang->story->relievedTwinsTip = "After product adjustment, the twin relationship of this story will be automatically removed, and the story will no longer be synchronized. Do you want to save?";
+$lang->story->batchEditTip = "{$lang->SRCommon} %sis twin stories, and this operation has been filtered.";
+
$lang->story->id = 'ID';
$lang->story->parent = 'Mẹ';
$lang->story->product = $lang->productCommon;
@@ -335,6 +343,7 @@ $lang->story->action->subdividestory = array('main' => '$date, được p
$lang->story->action->unlinkrelatedstory = array('main' => '$date, bị hủy bởi $actor từ Câu chuyện $extra .');
$lang->story->action->unlinkchildstory = array('main' => '$date, bị hủy bởi $actor Câu chuyện được phân rã $extra .');
$lang->story->action->recalledchange = array('main' => '$date, Undo changes by \$actor .');
+$lang->story->action->synctwins = array('main' => "\$date, the system judges that this story is adjusted synchronously due to the \$operate of twin story \$extra .", 'operate' => 'operateList');
/* Statistical statement. */
$lang->story->report = new stdclass();
@@ -459,3 +468,14 @@ $lang->story->featureBar['browse']['all'] = $lang->all;
$lang->story->featureBar['browse']['unclosed'] = $lang->story->unclosed;
$lang->story->featureBar['browse']['draft'] = $lang->story->statusList['draft'];
$lang->story->featureBar['browse']['reviewing'] = $lang->story->statusList['reviewing'];
+
+$lang->story->operateList = array();
+$lang->story->operateList['assigned'] = 'assigned';
+$lang->story->operateList['closed'] = 'closed';
+$lang->story->operateList['activated'] = 'activated';
+$lang->story->operateList['changed'] = 'changed';
+$lang->story->operateList['reviewed'] = 'reviewed';
+$lang->story->operateList['edited'] = 'edited';
+$lang->story->operateList['submitreview'] = 'submit review';
+$lang->story->operateList['recalledchange'] = 'recalled change';
+$lang->story->operateList['recalled'] = 'recalled review';
diff --git a/module/story/lang/zh-cn.php b/module/story/lang/zh-cn.php
index e3a8dbd10e..16fd7feddf 100644
--- a/module/story/lang/zh-cn.php
+++ b/module/story/lang/zh-cn.php
@@ -76,6 +76,11 @@ $lang->story->undetermined = '待定';
$lang->story->order = '排序';
$lang->story->saveDraft = '存为草稿';
$lang->story->doNotSubmit = '保存暂不提交';
+$lang->story->currentBranch = '当前%s';
+$lang->story->twins = '孪生需求';
+$lang->story->relieved = '解除';
+$lang->story->relievedTwins = '解除孪生需求';
+$lang->story->loadAllStories = '加载所有需求';
$lang->story->editAction = "编辑{$lang->SRCommon}";
$lang->story->changeAction = "变更{$lang->SRCommon}";
@@ -96,11 +101,20 @@ $lang->story->storyRound = '第 %s 轮估算';
$lang->story->float = "『%s』应当是正数,可以是小数。";
$lang->story->saveDraftSuccess = '存为草稿成功';
+$lang->story->changeSyncTip = "该需求的修改会同步到如下的孪生需求";
+$lang->story->syncTip = "孪生需求间除产品、分支 、模块、计划、阶段外均同步,孪生关系解除后不再同步";
+$lang->story->relievedTip = "孪生关系解除后无法恢复,需求的内容不再同步,是否解除?";
+$lang->story->assignSyncTip = "孪生需求均同步修改指派人";
+$lang->story->closeSyncTip = "孪生需求均同步关闭";
+$lang->story->activateSyncTip = "孪生需求均同步激活";
+$lang->story->relievedTwinsTip = '产品调整后,本需求自动解除孪生关系,需求不再同步,是否保存?';
+$lang->story->batchEditTip = "{$lang->SRCommon} %s为孪生需求,本次操作已被过滤。";
+
$lang->story->id = '编号';
$lang->story->parent = '父需求';
$lang->story->product = "所属{$lang->productCommon}";
$lang->story->project = "所属项目";
-$lang->story->branch = "分支/平台";
+$lang->story->branch = "平台/分支";
$lang->story->module = '所属模块';
$lang->story->moduleAB = '模块';
$lang->story->source = "来源";
@@ -133,6 +147,7 @@ $lang->story->assignedTo = '指派给';
$lang->story->assignedToAB = '指派';
$lang->story->assignedDate = '指派日期';
$lang->story->lastEditedBy = '最后修改';
+$lang->story->lastEditedByAB = '最后修改者';
$lang->story->lastEditedDate = '最后修改日期';
$lang->story->closedBy = '由谁关闭';
$lang->story->closedDate = '关闭日期';
@@ -284,6 +299,7 @@ $lang->story->checkAffection = '影响范围';
$lang->story->affectedProjects = "影响的{$lang->project->common}或{$lang->execution->common}";
$lang->story->affectedBugs = '影响的Bug';
$lang->story->affectedCases = '影响的用例';
+$lang->story->affectedTwins = '影响的孪生需求';
$lang->story->specTemplate = "建议参考的模板:作为一名<某种类型的用户>,我希望<达成某些目的>,这样可以<开发的价值>。";
$lang->story->needNotReview = '不需要评审';
@@ -309,6 +325,7 @@ $lang->story->errorDuplicateStory = $lang->SRCommon . '%s不存在';
$lang->story->confirmRecallChange = "撤销变更后,需求内容会回退至变更前的版本,您确定要撤销吗?";
$lang->story->confirmRecallReview = "您确定要撤回评审吗?";
$lang->story->noStoryToTask = "只有激活的{$lang->SRCommon}才能转为任务!";
+$lang->story->ignoreClosedStory = "{$lang->SRCommon} %s 状态为已关闭,本次操作已被过滤。";
$lang->story->form = new stdclass();
$lang->story->form->area = "该{$lang->SRCommon}所属范围";
@@ -344,6 +361,7 @@ $lang->story->action->subdividestory = array('main' => "\$date, 由 story->action->unlinkrelatedstory = array('main' => "\$date, 由 \$actor 移除相关{$lang->SRCommon} \$extra 。");
$lang->story->action->unlinkchildstory = array('main' => "\$date, 由 \$actor 移除细分{$lang->SRCommon} \$extra 。");
$lang->story->action->recalledchange = array('main' => "\$date, 由 \$actor 撤销变更。");
+$lang->story->action->synctwins = array('main' => "\$date, 系统判断由于孪生需求 \$extra \$operate,本需求同步调整。", 'operate' => 'operateList');
/* 统计报表。*/
$lang->story->report = new stdclass();
@@ -432,6 +450,7 @@ $lang->story->chosen->reviewedBy = '选择评审人...';
$lang->story->notice = new stdClass();
$lang->story->notice->closed = "您选择的{$lang->SRCommon}已经被关闭了!";
$lang->story->notice->reviewerNotEmpty = '该需求需要评审,评审人员不能为空。';
+$lang->story->notice->changePlan = '所属计划只能改为一条,修改后才能保存成功。';
$lang->story->convertToTask = new stdClass();
$lang->story->convertToTask->fieldList = array();
@@ -460,15 +479,27 @@ $lang->story->recallTip = array();
$lang->story->recallTip['actived'] = '该需求未发起评审流程,无需撤销操作';
$lang->story->subDivideTip = array();
-$lang->story->subDivideTip['subStory'] = '子需求无法细分';
-$lang->story->subDivideTip['notWait'] = '该需求%s,无法进行细分操作';
-$lang->story->subDivideTip['notActive'] = '需求不是激活状态,无法进行细分操作';
+$lang->story->subDivideTip['subStory'] = '子需求无法细分';
+$lang->story->subDivideTip['notWait'] = '该需求%s,无法进行细分操作';
+$lang->story->subDivideTip['notActive'] = '需求不是激活状态,无法进行细分操作';
+$lang->story->subDivideTip['twinsSplit'] = '孪生需求不可细分';
$lang->story->featureBar['browse']['all'] = '全部';
$lang->story->featureBar['browse']['unclosed'] = $lang->story->unclosed;
$lang->story->featureBar['browse']['draft'] = $lang->story->statusList['draft'];
$lang->story->featureBar['browse']['reviewing'] = $lang->story->statusList['reviewing'];
+$lang->story->operateList = array();
+$lang->story->operateList['assigned'] = '指派';
+$lang->story->operateList['closed'] = '关闭';
+$lang->story->operateList['activated'] = '激活';
+$lang->story->operateList['changed'] = '变更';
+$lang->story->operateList['reviewed'] = '评审';
+$lang->story->operateList['edited'] = '编辑';
+$lang->story->operateList['submitreview'] = '提交评审';
+$lang->story->operateList['recalledchange'] = '撤销变更';
+$lang->story->operateList['recalled'] = '撤销评审';
+
$lang->requirement->common = $lang->URCommon;
$lang->requirement->create = "提{$lang->URCommon}";
$lang->requirement->batchCreate = "批量创建";
@@ -493,3 +524,11 @@ $lang->requirement->batchAssignTo = "批量指派";
$lang->requirement->batchChangeModule = "批量修改模块";
$lang->requirement->submitReview = $lang->story->submitReview;
$lang->requirement->linkStory = "关联{$lang->SRCommon}";
+
+$lang->story->addBranch = '添加%s';
+$lang->story->deleteBranch = '删除%s';
+$lang->story->notice->branch = '每个分支会建立一个需求,需求间互为孪生关系。孪生需求间除产品、分支、模块、计划、阶段字段外均保持同步,后期您可以手动解除孪生关系。';
+
+$lang->story->relievedTwinsRelation = '解除孪生关系';
+$lang->story->relievedTwinsRelationTips = '孪生关系解除后无法恢复,需求的关闭将不再同步。';
+$lang->story->changeRelievedTwinsTips = '孪生关系解除后无法恢复,孪生需求间内容不再同步。';
diff --git a/module/story/model.php b/module/story/model.php
index 93a96f3a85..a333c5dcc6 100644
--- a/module/story/model.php
+++ b/module/story/model.php
@@ -49,11 +49,18 @@ class storyModel extends model
$story->executions = $this->dao->select('t1.project, t2.name, t2.status, t2.type, t2.multiple')->from(TABLE_PROJECTSTORY)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.project = t2.id')
- ->where('t1.story')->eq($storyID)
- ->andWhere('t2.type')->in('sprint,stage,kanban')
+ ->where('t2.type')->in('sprint,stage,kanban')
+ ->beginIF($story->twins)->andWhere('t1.story')->in(ltrim($story->twins, ',') . $story->id)
+ ->beginIF(!$story->twins)->andWhere('t1.story')->in($story->id)
->orderBy('t1.`order` DESC')
->fetchAll('project');
- $story->tasks = $this->dao->select('id, name, assignedTo, execution, project, status, consumed, `left`,type')->from(TABLE_TASK)->where('story')->eq($storyID)->andWhere('deleted')->eq(0)->orderBy('id DESC')->fetchGroup('execution');
+ $story->tasks = $this->dao->select('id, name, assignedTo, execution, project, status, consumed, `left`,type')->from(TABLE_TASK)
+ ->where('deleted')->eq(0)
+ ->beginIF($story->twins)->andWhere('story')->in(ltrim($story->twins, ',') . $story->id)
+ ->beginIF(!$story->twins)->andWhere('story')->in($story->id)
+ ->orderBy('id DESC')
+ ->fetchGroup('execution');
+
$story->stages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->eq($storyID)->fetchPairs('branch', 'stage');
//$story->bugCount = $this->dao->select('COUNT(*)')->alias('count')->from(TABLE_BUG)->where('story')->eq($storyID)->fetch('count');
//$story->caseCount = $this->dao->select('COUNT(*)')->alias('count')->from(TABLE_CASE)->where('story')->eq($storyID)->fetch('count');
@@ -159,13 +166,20 @@ class storyModel extends model
}
/* Get affected bugs. */
- $story->bugs = $this->dao->findByStory($story->id)->from(TABLE_BUG)
+ $story->bugs = $this->dao->select('*')->from(TABLE_BUG)
+ ->where('status')->ne('closed')
+ ->beginIF($story->twins)->andWhere('story')->in(ltrim($story->twins, ',') . $story->id)
+ ->beginIF(!$story->twins)->andWhere('story')->in($story->id)
->andWhere('status')->ne('closed')
->andWhere('deleted')->eq(0)
->orderBy('id desc')->fetchAll();
/* Get affected cases. */
- $story->cases = $this->dao->findByStory($story->id)->from(TABLE_CASE)->andWhere('deleted')->eq(0)->fetchAll();
+ $story->cases = $this->dao->select('*')->from(TABLE_CASE)
+ ->where('deleted')->eq(0)
+ ->beginIF($story->twins)->andWhere('story')->in(ltrim($story->twins, ',') . $story->id)
+ ->beginIF(!$story->twins)->andWhere('story')->in($story->id)
+ ->fetchAll();
return $story;
}
@@ -228,173 +242,223 @@ class storyModel extends model
->join('assignedTo', '')
->join('mailto', ',')
->stripTags($this->config->story->editor->create['id'], $this->config->allowedTags)
- ->remove('files,labels,reviewer,needNotReview,newStory,uid,contactListMenu,URS,region,lane,ticket')
+ ->remove('files,labels,reviewer,needNotReview,newStory,uid,contactListMenu,URS,region,lane,ticket,branches,modules,plans')
->get();
/* Check repeat story. */
$result = $this->loadModel('common')->removeDuplicate('story', $story, "product={$story->product}");
if(isset($result['stop']) and $result['stop']) return array('status' => 'exists', 'id' => $result['duplicate']);
-
if($story->status != 'draft' and $this->checkForceReview()) $story->status = 'reviewing';
- if(strpos('draft,reviewing', $story->status) !== false) $story->stage = $this->post->plan > 0 ? 'planned' : 'wait';
$story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->create['id'], $this->post->uid);
- $requiredFields = "," . $this->config->story->create->requiredFields . ",";
-
- if($story->type == 'requirement') $requiredFields = str_replace(',plan,', ',', $requiredFields);
- if(strpos($requiredFields, ',estimate,') !== false)
+ $product = $this->loadModel('product')->getById($story->product);
+ if($product->type == 'normal' or $story->type == 'requirement')
{
- if(strlen(trim($story->estimate)) == 0) dao::$errors['estimate'] = sprintf($this->lang->error->notempty, $this->lang->story->estimate);
- $requiredFields = str_replace(',estimate,', ',', $requiredFields);
+ $this->post->branches = isset($story->branch) ? array($story->branch) : array(0 => 0);
+ $this->post->modules = isset($story->module) ? array($story->module) : array(0 => 0);
+ $this->post->plans = isset($story->plan) ? array($story->plan) : array(0 => 0);
}
- $requiredFields = trim($requiredFields, ',');
-
- $this->dao->insert(TABLE_STORY)->data($story, 'spec,verify')
- ->autoCheck()
- ->checkIF($story->notifyEmail, 'notifyEmail', 'email')
- ->batchCheck($requiredFields, 'notempty')
- ->checkFlow()
- ->exec();
-
- if(!dao::isError())
+ /* check module */
+ $requiredFields = "," . $this->config->story->create->requiredFields . ",";
+ if(strpos($requiredFields, ',module,') !== false)
{
- $storyID = $this->dao->lastInsertID();
-
- /* Fix bug #21992, user story have no parent story. */
- if(isset($story->parent) and $story->parent)
+ foreach($this->post->modules as $module)
{
- $stories = array($storyID);
- $this->subdivide($story->parent, $stories);
- }
-
- if(!empty($story->plan))
- {
- $this->updateStoryOrderOfPlan($storyID, $story->plan); // Set story order in this plan.
- $this->loadModel('action')->create('productplan', $story->plan, 'linkstory', '', $storyID);
- }
-
- $this->file->updateObjectID($this->post->uid, $storyID, $story->type);
- $files = $this->file->saveUpload($story->type, $storyID, 1);
-
- $data = new stdclass();
- $data->story = $storyID;
- $data->version = 1;
- $data->title = $story->title;
- $data->spec = $story->spec;
- $data->verify = $story->verify;
- $data->files = join(',', array_keys($files));
- $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec();
-
- /* Save the story reviewer to storyreview table. */
- if(isset($_POST['reviewer']))
- {
- foreach($this->post->reviewer as $reviewer)
+ if(empty($module))
{
- if(empty($reviewer)) continue;
-
- $reviewData = new stdclass();
- $reviewData->story = $storyID;
- $reviewData->version = 1;
- $reviewData->reviewer = $reviewer;
- $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
+ dao::$errors[] = sprintf($this->lang->error->notempty, $this->lang->story->module);
+ return false;
}
}
+ }
- /* Project or execution linked story. */
- if($executionID != 0)
+ $storyIds = array();
+ $storyFile = array();
+ $mainStoryID = 0;
+ foreach($this->post->branches as $key => $branch)
+ {
+ $story->branch = $branch;
+ $story->module = $this->post->modules[$key];
+ $story->plan = $this->post->plans[$key];
+
+ if(strpos('draft,reviewing', $story->status) !== false) $story->stage = $this->post->plan > 0 ? 'planned' : 'wait';
+ if($story->type == 'requirement') $requiredFields = str_replace(',plan,', ',', $requiredFields);
+ if(strpos($requiredFields, ',estimate,') !== false)
{
- $this->linkStory($executionID, $this->post->product, $storyID);
- if($executionID != $this->session->project) $this->linkStory($this->session->project, $this->post->product, $storyID);
-
- $this->loadModel('kanban');
-
- $laneID = isset($output['laneID']) ? $output['laneID'] : 0;
- if(isset($_POST['lane'])) $laneID = $_POST['lane'];
-
- $columnID = $this->kanban->getColumnIDByLaneID($laneID, 'backlog');
- if(empty($columnID)) $columnID = isset($output['columnID']) ? $output['columnID'] : 0;
-
- if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'story', $storyID);
- if(empty($laneID) or empty($columnID)) $this->kanban->updateLane($executionID, 'story');
+ if(strlen(trim($story->estimate)) == 0) dao::$errors['estimate'] = sprintf($this->lang->error->notempty, $this->lang->story->estimate);
+ $requiredFields = str_replace(',estimate,', ',', $requiredFields);
}
- if(is_array($this->post->URS))
+ $requiredFields = trim($requiredFields, ',');
+
+ $this->dao->insert(TABLE_STORY)->data($story, 'spec,verify')
+ ->autoCheck()
+ ->checkIF($story->notifyEmail, 'notifyEmail', 'email')
+ ->batchCheck($requiredFields, 'notempty')
+ ->checkFlow()
+ ->exec();
+
+ if(dao::isError()) return false;
+
+ if(!dao::isError())
{
- foreach($this->post->URS as $URID)
+ $storyID = $this->dao->lastInsertID();
+
+ /* Fix bug #21992, user story have no parent story. */
+ if(isset($story->parent) and $story->parent)
{
- $requirement = $this->getByID($URID);
- $data = new stdclass();
- $data->product = $story->product;
- $data->AType = 'requirement';
- $data->relation = 'subdivideinto';
- $data->BType = 'story';
- $data->AID = $URID;
- $data->BID = $storyID;
- $data->AVersion = $requirement->version;
- $data->BVersion = 1;
- $data->extra = 1;
-
- $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec();
-
- $data->AType = 'story';
- $data->relation = 'subdividedfrom';
- $data->BType = 'requirement';
- $data->AID = $storyID;
- $data->BID = $URID;
- $data->AVersion = 1;
- $data->BVersion = $requirement->version;
-
- $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec();
+ $stories = array($storyID);
+ $this->subdivide($story->parent, $stories);
}
- }
- if($bugID > 0)
- {
- if(($this->config->edition == 'biz' || $this->config->edition == 'max')) $oldBug = $this->dao->select('feedback, status')->from(TABLE_BUG)->where('id')->eq($bugID)->fetch();
-
- $bug = new stdclass();
- $bug->toStory = $storyID;
- $bug->status = 'closed';
- $bug->resolution = 'tostory';
- $bug->resolvedBy = $this->app->user->account;
- $bug->resolvedDate = $now;
- $bug->closedBy = $this->app->user->account;
- $bug->closedDate = $now;
- $bug->assignedTo = 'closed';
- $bug->assignedDate = $now;
- $this->dao->update(TABLE_BUG)->data($bug)->where('id')->eq($bugID)->exec();
-
- $this->loadModel('action')->create('bug', $bugID, 'ToStory', '', $storyID);
- $this->action->create('bug', $bugID, 'Closed');
-
- if(($this->config->edition == 'biz' || $this->config->edition == 'max') && !dao::isError() && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, 'closed', $oldBug->status);
-
- /* add files to story from bug. */
- $files = $this->dao->select('*')->from(TABLE_FILE)
- ->where('objectType')->eq('bug')
- ->andWhere('objectID')->eq($bugID)
- ->fetchAll();
- if(!empty($files))
+ if(!empty($story->plan))
{
- foreach($files as $file)
+ $this->updateStoryOrderOfPlan($storyID, $story->plan); // Set story order in this plan.
+ $this->loadModel('action')->create('productplan', $story->plan, 'linkstory', '', $storyID);
+ }
+
+ $this->file->updateObjectID($this->post->uid, $storyID, $story->type);
+ $files = $this->file->saveUpload($story->type, $storyID, 1);
+ /* Multi branch sync files */
+ !empty($files) ? $storyFile = $files : $files = $storyFile;
+
+ $data = new stdclass();
+ $data->story = $storyID;
+ $data->version = 1;
+ $data->title = $story->title;
+ $data->spec = $story->spec;
+ $data->verify = $story->verify;
+ $data->files = join(',', array_keys($files));
+ $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec();
+
+ /* Save the story reviewer to storyreview table. */
+ if(isset($_POST['reviewer']))
+ {
+ foreach($this->post->reviewer as $reviewer)
{
- $file->objectType = 'story';
- $file->objectID = $storyID;
- unset($file->id);
- $this->dao->insert(TABLE_FILE)->data($file)->exec();
+ if(empty($reviewer)) continue;
+
+ $reviewData = new stdclass();
+ $reviewData->story = $storyID;
+ $reviewData->version = 1;
+ $reviewData->reviewer = $reviewer;
+ $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
}
}
+
+ /* Project or execution linked story. */
+ if($executionID != 0)
+ {
+ $this->linkStory($executionID, $this->post->product, $storyID);
+ if($this->config->systemMode == 'new' and $executionID != $this->session->project) $this->linkStory($this->session->project, $this->post->product, $storyID);
+
+ $this->loadModel('kanban');
+
+ $laneID = isset($output['laneID']) ? $output['laneID'] : 0;
+ if(isset($_POST['lane'])) $laneID = $_POST['lane'];
+
+ $columnID = $this->kanban->getColumnIDByLaneID($laneID, 'backlog');
+ if(empty($columnID)) $columnID = isset($output['columnID']) ? $output['columnID'] : 0;
+
+ if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'story', $storyID);
+ if(empty($laneID) or empty($columnID)) $this->kanban->updateLane($executionID, 'story');
+ }
+
+ if(is_array($this->post->URS))
+ {
+ foreach($this->post->URS as $URID)
+ {
+ $requirement = $this->getByID($URID);
+ $data = new stdclass();
+ $data->product = $story->product;
+ $data->AType = 'requirement';
+ $data->relation = 'subdivideinto';
+ $data->BType = 'story';
+ $data->AID = $URID;
+ $data->BID = $storyID;
+ $data->AVersion = $requirement->version;
+ $data->BVersion = 1;
+ $data->extra = 1;
+
+ $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec();
+
+ $data->AType = 'story';
+ $data->relation = 'subdividedfrom';
+ $data->BType = 'requirement';
+ $data->AID = $storyID;
+ $data->BID = $URID;
+ $data->AVersion = 1;
+ $data->BVersion = $requirement->version;
+
+ $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec();
+ }
+ }
+
+ if($bugID > 0)
+ {
+ if(($this->config->edition == 'biz' || $this->config->edition == 'max')) $oldBug = $this->dao->select('feedback, status')->from(TABLE_BUG)->where('id')->eq($bugID)->fetch();
+
+ $bug = new stdclass();
+ $bug->toStory = $storyID;
+ $bug->status = 'closed';
+ $bug->resolution = 'tostory';
+ $bug->resolvedBy = $this->app->user->account;
+ $bug->resolvedDate = $now;
+ $bug->closedBy = $this->app->user->account;
+ $bug->closedDate = $now;
+ $bug->assignedTo = 'closed';
+ $bug->assignedDate = $now;
+ $this->dao->update(TABLE_BUG)->data($bug)->where('id')->eq($bugID)->exec();
+
+ $this->loadModel('action')->create('bug', $bugID, 'ToStory', '', $storyID);
+ $this->action->create('bug', $bugID, 'Closed');
+
+ if(($this->config->edition == 'biz' || $this->config->edition == 'max') && !dao::isError() && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, 'closed', $oldBug->status);
+
+ /* add files to story from bug. */
+ $files = $this->dao->select('*')->from(TABLE_FILE)
+ ->where('objectType')->eq('bug')
+ ->andWhere('objectID')->eq($bugID)
+ ->fetchAll();
+ if(!empty($files))
+ {
+ foreach($files as $file)
+ {
+ $file->objectType = 'story';
+ $file->objectID = $storyID;
+ unset($file->id);
+ $this->dao->insert(TABLE_FILE)->data($file)->exec();
+ }
+ }
+ }
+ if(!defined('TUTORIAL')) $this->setStage($storyID);
+ if(!dao::isError()) $this->loadModel('score')->create('story', 'create',$storyID);
+
+ /* Callback the callable method to process the related data for object that is transfered to story. */
+ if($from && is_callable(array($this, $this->config->story->fromObjects[$from]['callback']))) call_user_func(array($this, $this->config->story->fromObjects[$from]['callback']), $storyID);
+
+ $storyIds[] = $storyID;
+ if(empty($mainStoryID)) $mainStoryID = $storyID;
}
- if(!defined('TUTORIAL')) $this->setStage($storyID);
- if(!dao::isError()) $this->loadModel('score')->create('story', 'create',$storyID);
-
- /* Callback the callable method to process the related data for object that is transfered to story. */
- if($from && is_callable(array($this, $this->config->story->fromObjects[$from]['callback']))) call_user_func(array($this, $this->config->story->fromObjects[$from]['callback']), $storyID);
-
- return array('status' => 'created', 'id' => $storyID);
}
- return false;
+
+ /* bind twins story id */
+ if(count($storyIds) > 1)
+ {
+ foreach($storyIds as $twinsStoryID)
+ {
+ $twinsArr = array();
+ foreach($storyIds as $idItem)
+ {
+ if($idItem != $twinsStoryID) $twinsArr[] = $idItem;
+ $twins = ',' . implode(',', $twinsArr) . ',';
+ $this->dao->update(TABLE_STORY)->set('twins')->eq($twins)->where('id')->eq($twinsStoryID)->exec();
+ }
+ }
+ }
+
+ return array('status' => 'created', 'id' => $mainStoryID, 'ids' => $storyIds);
}
/**
@@ -714,7 +778,7 @@ class storyModel extends model
->get();
$story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->change['id'], $this->post->uid);
- $this->dao->update(TABLE_STORY)->data($story, 'spec,verify,deleteFiles')
+ $this->dao->update(TABLE_STORY)->data($story, 'spec,verify,deleteFiles,relievedTwins')
->autoCheck()
->batchCheck($this->config->story->change->requiredFields, 'notempty')
->checkFlow()
@@ -739,6 +803,16 @@ class storyModel extends model
$data->files = $story->files = $addedFiles . trim($storyFiles, ',');
$this->dao->insert(TABLE_STORYSPEC)->data($data)->exec();
+ /* Sync twins. */
+ if(!isset($story->relievedTwins) and !empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $data->story = $twinID;
+ $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec();
+ }
+ }
+
/* IF is story and has changed, update its relation version to new. */
if($oldStory->type == 'story')
{
@@ -767,6 +841,16 @@ class storyModel extends model
$reviewData->version = $story->version;
$reviewData->reviewer = $reviewer;
$this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
+
+ /* Sync twins. */
+ if(!isset($story->relievedTwins) and !empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $reviewData->story = $twinID;
+ $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
+ }
+ }
}
if($reviewerHasChanged)
@@ -776,7 +860,19 @@ class storyModel extends model
}
}
- return common::createChanges($oldStory, $story);
+ $changes = common::createChanges($oldStory, $story);
+
+ if(isset($story->relievedTwins))
+ {
+ $this->dbh->exec("UPDATE " . TABLE_STORY . " SET twins = REPLACE(twins, ',$storyID,', ',') WHERE `product` = $oldStory->product");
+ $this->dao->update(TABLE_STORY)->set('twins')->eq('')->where('id')->eq($storyID)->orWhere('twins')->eq(',')->exec();
+ }
+ elseif(!empty($oldStory->twins))
+ {
+ $this->syncTwins($oldStory->id, $oldStory->twins, $changes, 'Changed');
+ }
+
+ return $changes;
}
}
@@ -803,6 +899,17 @@ class storyModel extends model
return false;
}
+ $storyPlan = !empty($_POST['plan']) ? array_filter($_POST['plan']) : array();
+ if(count($storyPlan) > 1)
+ {
+ $oldStoryPlan = !empty($oldStory->planTitle) ? array_keys($oldStory->planTitle) : array();
+ if(!empty(array_diff($storyPlan,$oldStoryPlan)) or !empty(array_diff($oldStoryPlan,$storyPlan)))
+ {
+ dao::$errors[] = $this->lang->story->notice->changePlan;
+ return false;
+ }
+ }
+
$story = fixer::input('post')
->cleanInt('product,module,pri,duplicateStory')
->cleanFloat('estimate')
@@ -836,6 +943,14 @@ class storyModel extends model
->remove('files,labels,comment,contactListMenu,stages,reviewer,needNotReview')
->get();
+ /* Relieve twins when change product. */
+ if(!empty($oldStory->twins) and $story->product != $oldStory->product)
+ {
+ $this->dbh->exec("UPDATE " . TABLE_STORY . " SET twins = REPLACE(twins, ',$storyID,', ',') WHERE `product` = $oldStory->product");
+ $this->dao->update(TABLE_STORY)->set('twins')->eq('')->where('id')->eq($storyID)->orWhere('twins')->eq(',')->exec();
+ $oldStory->twins = '';
+ }
+
if($oldStory->type == 'story' and !isset($story->linkStories)) $story->linkStories = '';
if($oldStory->type == 'requirement' and !isset($story->linkRequirements)) $story->linkRequirements = '';
if($oldStory->status == 'changing' and $story->status == 'draft') $story->status = 'changing';
@@ -887,6 +1002,19 @@ class storyModel extends model
->beginIF($oldStory->status == 'reviewing')->andWhere('reviewer')->notin(implode(',', $_POST['reviewer']))
->exec();
+ /* Sync twins. */
+ if(!empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $this->dao->delete()->from(TABLE_STORYREVIEW)
+ ->where('story')->eq($twinID)
+ ->andWhere('version')->eq($oldStory->version)
+ ->beginIF($oldStory->status == 'reviewing')->andWhere('reviewer')->notin(implode(',', $_POST['reviewer']))
+ ->exec();
+ }
+ }
+
foreach($_POST['reviewer'] as $reviewer)
{
if($oldStory->status == 'reviewing' and in_array($reviewer, array_keys($oldReviewer))) continue;
@@ -896,6 +1024,16 @@ class storyModel extends model
$reviewData->version = $oldStory->version;
$reviewData->reviewer = $reviewer;
$this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
+
+ /* Sync twins. */
+ if(!empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $reviewData->story = $twinID;
+ $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
+ }
+ }
}
if($oldStory->status == 'reviewing') $story = $this->updateStoryByReview($storyID, $oldStory, $story);
@@ -933,6 +1071,18 @@ class storyModel extends model
$data->verify = $story->verify;
$data->files = $story->files = $addedFiles . trim($storyFiles, ',');
$this->dao->update(TABLE_STORYSPEC)->data($data)->where('story')->eq((int)$storyID)->andWhere('version')->eq($oldStory->version)->exec();
+
+ /* Sync twins. */
+ if(!empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $this->dao->update(TABLE_STORYSPEC)->data($data)
+ ->where('story')->eq((int)$twinID)
+ ->andWhere('version')->eq($oldStory->version)
+ ->exec();
+ }
+ }
}
if($story->product != $oldStory->product)
@@ -1062,6 +1212,8 @@ class storyModel extends model
if(isset($story->finalResult)) $this->recordReviewAction($story);
}
+ if(!empty($oldStory->twins)) $this->syncTwins($oldStory->id, $oldStory->twins, $changes, 'Edited');
+
return true;
}
}
@@ -1114,7 +1266,7 @@ class storyModel extends model
if(empty($childrenStatus)) return $this->dao->update(TABLE_STORY)->set('parent')->eq('0')->where('id')->eq($parentID)->exec();
$status = $oldParentStory->status;
- if(count($childrenStatus) == 1 and $oldParentStory->status != 'changing')
+ if(count($childrenStatus) == 1 and $oldParentStory->status != 'changing' and $oldParentStory->status != 'reviewing')
{
$status = current($childrenStatus);
if($status == 'draft' or $status == 'changing') $status = 'active';
@@ -1475,7 +1627,28 @@ class storyModel extends model
/* Fix bug #671. */
$this->lang->story->closedReason = $this->lang->story->rejectedReason;
- $this->dao->update(TABLE_STORYREVIEW)->set('result')->eq($this->post->result)->set('reviewDate')->eq($now)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->eq($this->app->user->account)->exec();
+ $this->dao->update(TABLE_STORYREVIEW)
+ ->set('result')->eq($this->post->result)
+ ->set('reviewDate')->eq($now)
+ ->where('story')->eq($storyID)
+ ->andWhere('version')->eq($oldStory->version)
+ ->andWhere('reviewer')->eq($this->app->user->account)
+ ->exec();
+
+ /* Sync twins. */
+ if(!empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $this->dao->update(TABLE_STORYREVIEW)
+ ->set('result')->eq($this->post->result)
+ ->set('reviewDate')->eq($now)
+ ->where('story')->eq($twinID)
+ ->andWhere('version')->eq($oldStory->version)
+ ->andWhere('reviewer')->eq($this->app->user->account)
+ ->exec();
+ }
+ }
$story = $this->updateStoryByReview($storyID, $oldStory, $story);
@@ -1507,6 +1680,8 @@ class storyModel extends model
$this->action->logHistory($actionID, $changes);
}
+ if(!empty($oldStory->twins)) $this->syncTwins($oldStory->id, $oldStory->twins, $changes, 'Reviewed');
+
return true;
}
@@ -1525,17 +1700,23 @@ class storyModel extends model
$actions = array();
$this->loadModel('action');
- $oldStories = $this->getByList($storyIdList);
- $hasResult = $this->dao->select('story,version,result')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->andWhere('reviewer')->eq($this->app->user->account)->andWhere('result')->ne('')->orderBy('version')->fetchAll('story');
- $reviewerList = $this->dao->select('story,reviewer,result,version')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->orderBy('version')->fetchGroup('story', 'reviewer');
+ $reviewedTwins = array();
+ $oldStories = $this->getByList($storyIdList);
+ $hasResult = $this->dao->select('story,version,result')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->andWhere('reviewer')->eq($this->app->user->account)->andWhere('result')->ne('')->orderBy('version')->fetchAll('story');
+ $reviewerList = $this->dao->select('story,reviewer,result,version')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->orderBy('version')->fetchGroup('story', 'reviewer');
foreach($storyIdList as $storyID)
{
if(!$storyID) continue;
$isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ',');
$oldStory = $oldStories[$storyID];
-
if($oldStory->status != 'reviewing') continue;
+
+ foreach($reviewerList[$storyID] as $reviewer => $reviewerInfo)
+ {
+ if($reviewerInfo->version != $oldStory->version) unset($reviewerList[$storyID][$reviewer]);
+ }
+
if(!in_array($this->app->user->account, array_keys($reviewerList[$storyID])) and $isSuperReviewer === false) continue;
if(isset($hasResult[$storyID]) and $hasResult[$storyID]->version == $oldStories[$storyID]->version) continue;
if($oldStory->version > 1 and $result == 'reject') continue;
@@ -1549,6 +1730,21 @@ class storyModel extends model
$this->dao->update(TABLE_STORYREVIEW)->set('result')->eq($result)->set('reviewDate')->eq($now)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->eq($this->app->user->account)->exec();
+ /* Sync twins. */
+ if(!empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $this->dao->update(TABLE_STORYREVIEW)
+ ->set('result')->eq($result)
+ ->set('reviewDate')->eq($now)
+ ->where('story')->eq($twinID)
+ ->andWhere('version')->eq($oldStory->version)
+ ->andWhere('reviewer')->eq($this->app->user->account)
+ ->exec();
+ }
+ }
+
/* Update the story status by review rules. */
$reviewedBy = explode(',', trim($story->reviewedBy, ','));
if($isSuperReviewer !== false)
@@ -1571,6 +1767,19 @@ class storyModel extends model
$story->id = $storyID;
$story->version = $oldStory->version;
$actions[$storyID] = $this->recordReviewAction($story, $result, $reason);
+
+ /* Sync twins. */
+ $changes = common::createChanges($oldStory, $story);
+ if(!empty($oldStory->twins))
+ {
+ $twins = $oldStory->twins;
+ foreach(explode(',', $twins) as $twinID)
+ {
+ if(in_array($twinID, $storyIdList) or isset($reviewedTwins[$twinID])) $twins = str_replace(",$twinID,", ',', $twins);
+ }
+ $this->syncTwins($storyID, trim($twins, ','), $changes, 'Reviewed');
+ foreach(explode(',', trim($twins, ',')) as $reviewedID) $reviewedTwins[$reviewedID] = $reviewedID;
+ }
}
return $actions;
@@ -1585,11 +1794,26 @@ class storyModel extends model
*/
public function recallReview($storyID)
{
- $story = $this->getById($storyID);
- $isChanged = $story->changedBy ? true : false;
- $status = $isChanged ? 'changing' : 'draft';
- $this->dao->update(TABLE_STORY)->set('status')->eq($status)->where('id')->eq($storyID)->exec();
- $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($story->version)->exec();
+ $oldStory = $this->getById($storyID);
+ $isChanged = $oldStory->changedBy ? true : false;
+
+ $story = clone $oldStory;
+ $story->status = $isChanged ? 'changing' : 'draft';
+ $this->dao->update(TABLE_STORY)->set('status')->eq($story->status)->where('id')->eq($storyID)->exec();
+
+ $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->exec();
+
+ /* Sync twins. */
+ if(!empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($twinID)->andWhere('version')->eq($oldStory->version)->exec();
+ }
+ }
+
+ $changes = common::createChanges($oldStory, $story);
+ if(!empty($oldStory->twins)) $this->syncTwins($storyID, $oldStory->twins, $changes, 'recalled');
}
/**
@@ -1601,16 +1825,31 @@ class storyModel extends model
*/
public function recallChange($storyID)
{
- $story = $this->getById($storyID);
+ $oldStory = $this->getById($storyID);
/* Update story title and version and status. */
- $preVersion = $story->version - 1;
- $preTitle = $this->dao->select('title')->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->eq($preVersion)->fetch('title');
- $this->dao->update(TABLE_STORY)->set('title')->eq($preTitle)->set('version')->eq($preVersion)->set('status')->eq('active')->where('id')->eq($storyID)->exec();
+ $story = clone $oldStory;
+ $story->version = $oldStory->version - 1;
+ $story->title = $this->dao->select('title')->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->eq($story->version)->fetch('title');
+ $story->status = 'active';
+ $this->dao->update(TABLE_STORY)->set('title')->eq($story->title)->set('version')->eq($story->version)->set('status')->eq($story->status)->where('id')->eq($storyID)->exec();
/* Delete versions that is after this version. */
- $this->dao->delete()->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->eq($story->version)->exec();
- $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($story->version)->exec();
+ $this->dao->delete()->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->eq($oldStory->version)->exec();
+ $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->exec();
+
+ /* Sync twins. */
+ if(!empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $this->dao->delete()->from(TABLE_STORYSPEC)->where('story')->eq($twinID)->andWHere('version')->eq($oldStory->version)->exec();
+ $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($twinID)->andWhere('version')->eq($oldStory->version)->exec();
+ }
+ }
+
+ $changes = common::createChanges($oldStory, $story);
+ if(!empty($oldStory->twins)) $this->syncTwins($storyID, $oldStory->twins, $changes, 'recalledChange');
}
/**
@@ -1643,6 +1882,15 @@ class storyModel extends model
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->exec();
+ /* Sync twins. */
+ if(!empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($twinID)->andWhere('version')->eq($oldStory->version)->exec();
+ }
+ }
+
if(isset($_POST['reviewer']))
{
foreach($this->post->reviewer as $reviewer)
@@ -1654,12 +1902,25 @@ class storyModel extends model
$reviewData->version = $oldStory->version;
$reviewData->reviewer = $reviewer;
$this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
+
+ /* Sync twins. */
+ if(!empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $reviewData->story = $twinID;
+ $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec();
+ }
+ }
}
$story->status = 'reviewing';
}
$this->dao->update(TABLE_STORY)->data($story, 'reviewer')->where('id')->eq($storyID)->exec();
- if(!dao::isError()) return common::createChanges($oldStory, $story);
+
+ $changes = common::createChanges($oldStory, $story);
+ if(!empty($oldStory->twins)) $this->syncTwins($storyID, $oldStory->twins, $changes, 'submitReview');
+ if(!dao::isError()) return $changes;
return false;
}
@@ -1705,10 +1966,6 @@ class storyModel extends model
}
if(dao::isError()) return print(js::error(dao::getError()));
-
- $isonlybody = isonlybody();
- unset($_GET['onlybody']);
- return print(js::locate(helper::createLink('product', 'browse', "productID=$oldStory->product&branch=0&browseType=unclosed&queryID=0&type=story"), $isonlybody ? 'parent.parent' : 'parent'));
}
else
{
@@ -1763,6 +2020,7 @@ class storyModel extends model
->stripTags($this->config->story->editor->close['id'], $this->config->allowedTags)
->removeIF($this->post->closedReason != 'duplicate', 'duplicateStory')
->removeIF($this->post->closedReason != 'subdivided', 'childStories')
+ ->remove('closeSync')
->get();
if(!empty($story->duplicateStory))
@@ -1797,7 +2055,21 @@ class storyModel extends model
if(($this->config->edition == 'biz' || $this->config->edition == 'max') && $oldStory->feedback) $this->loadModel('feedback')->updateStatus('story', $oldStory->feedback, $story->status, $oldStory->status);
}
- return common::createChanges($oldStory, $story);
+
+ $changes = common::createChanges($oldStory, $story);
+ if($this->post->closeSync)
+ {
+ /* batchUnset twinID from twins.*/
+ $replaceSql = "UPDATE " . TABLE_STORY . " SET twins = REPLACE(twins,',$storyID,', ',') WHERE `product` = $oldStory->product";
+ $this->dbh->exec($replaceSql);
+
+ /* Update twins to empty by twinID and if twins eq ','.*/
+ $this->dao->update(TABLE_STORY)->set('twins')->eq('')->where('id')->eq($storyID)->orWhere('twins')->eq(',')->exec();
+
+ if(!dao::isError()) $this->loadModel('action')->create('story', $storyID, 'relieved');
+ }
+ if(!empty($oldStory->twins) and !$this->post->closeSync) $this->syncTwins($storyID, $oldStory->twins, $changes, 'Closed');
+ return $changes;
}
/**
@@ -1923,12 +2195,17 @@ class storyModel extends model
$oldStoryStages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->in($storyIdList)->fetchGroup('story', 'branch');
$unlinkPlans = array();
$link2Plans = array();
+ if(empty($plan))
+ {
+ $plan = new stdClass();
+ $plan->branch = BRANCH_MAIN;
+ }
/* Cycle every story and process it's plan and stage. */
foreach($storyIdList as $storyID)
{
$oldStory = $oldStories[$storyID];
- if($oldStory->branch != BRANCH_MAIN and $oldStory->branch != $plan->branch and $plan->branch != BRANCH_MAIN) continue;
+ if($oldStory->branch != BRANCH_MAIN and !in_array($oldStory->branch, explode(',', $plan->branch)) and $plan->branch != BRANCH_MAIN) continue;
/* Ignore parent story, closed story and story linked to this plan already. */
if($oldStory->parent < 0) continue;
@@ -1958,12 +2235,29 @@ class storyModel extends model
->where('product')->eq($oldStory->product)
->andWhere('id')->in($oldStory->plan)
->fetchPairs();
+ foreach($branchPlanPairs as $branches => $planParis)
+ {
+ foreach(explode(',', $branches) as $branch)
+ {
+ $branchPlanPairs[$branch] = $planParis;
+ }
+ unset($branchPlanPairs[$branches]);
+ }
if($planID == 0 and !empty($oldStory->plan) and isset($branchPlanPairs[0]) and $branchPlanPairs[0] != $planID) $unlinkPlans[$branchPlanPairs[0]] = empty($unlinkPlans[$branchPlanPairs[0]]) ? $storyID : "{$unlinkPlans[$branchPlanPairs[0]]},$storyID";
if($planID != 0)
{
- if(!empty($oldStory->plan) and isset($branchPlanPairs[$plan->branch]) and $branchPlanPairs[$plan->branch] != $planID) $unlinkPlans[$branchPlanPairs[$plan->branch]] = empty($unlinkPlans[$branchPlanPairs[$plan->branch]]) ? $storyID : "{$unlinkPlans[$branchPlanPairs[$plan->branch]]},$storyID";
- if(isset($branchPlanPairs[$plan->branch])) $branchPlanPairs[$plan->branch] = $planID;
- $story->plan = $oldStory->plan ? implode(',', $branchPlanPairs) : $planID;
+ if(!empty($oldStory->plan))
+ {
+ foreach(explode(',', $plan->branch) as $planBranch)
+ {
+ if(isset($branchPlanPairs[$planBranch]) and $branchPlanPairs[$planBranch] != $planID)
+ {
+ $unlinkPlans[$branchPlanPairs[$planBranch]] = empty($unlinkPlans[$branchPlanPairs[$planBranch]]) ? $storyID : "{$unlinkPlans[$branchPlanPairs[$planBranch]]},$storyID";
+ }
+ }
+ }
+ foreach(explode(',', $plan->branch) as $planBranch) if(isset($branchPlanPairs[$planBranch])) $branchPlanPairs[$planBranch] = $planID;
+ $story->plan = $oldStory->plan ? implode(',', array_unique($branchPlanPairs)) : $planID;
$story->plan = trim($story->plan, ',');
if($story->plan != $oldStory->plan and !empty($planID)) $link2Plans[$planID] = empty($link2Plans[$planID]) ? $storyID : "{$link2Plans[$planID]},$storyID";
}
@@ -1975,14 +2269,17 @@ class storyModel extends model
if($oldStory->stage == 'wait') $story->stage = 'planned';
if($this->session->currentProductType and $this->session->currentProductType != 'normal' and $oldStory->branch == 0)
{
- if(!isset($oldStoryStages[$storyID][$plan->branch]))
+ foreach(explode(',', $plan->branch) as $planBranch)
{
- $story->stage = 'planned';
- $newStoryStage = new stdclass();
- $newStoryStage->story = $storyID;
- $newStoryStage->branch = $plan->branch;
- $newStoryStage->stage = $story->stage;
- $this->dao->insert(TABLE_STORYSTAGE)->data($newStoryStage)->autoCheck()->exec();
+ if(!isset($oldStoryStages[$storyID][$planBranch]))
+ {
+ $story->stage = 'planned';
+ $newStoryStage = new stdclass();
+ $newStoryStage->story = $storyID;
+ $newStoryStage->branch = $planBranch;
+ $newStoryStage->stage = $story->stage;
+ $this->dao->insert(TABLE_STORYSTAGE)->data($newStoryStage)->autoCheck()->exec();
+ }
}
}
}
@@ -2312,7 +2609,10 @@ class storyModel extends model
$story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->assignto['id'], $this->post->uid);
$this->dao->update(TABLE_STORY)->data($story)->autoCheck()->checkFlow()->where('id')->eq((int)$storyID)->exec();
- if(!dao::isError()) return common::createChanges($oldStory, $story);
+
+ $changes = common::createChanges($oldStory, $story);
+ if(!empty($oldStory->twins)) $this->syncTwins($storyID, $oldStory->twins, $changes, 'Assigned');
+ if(!dao::isError()) return $changes;
return false;
}
@@ -2329,9 +2629,15 @@ class storyModel extends model
$storyIdList = $this->post->storyIdList;
$assignedTo = $this->post->assignedTo;
$oldStories = $this->getByList($storyIdList);
+ $ignoreStories = '';
foreach($storyIdList as $storyID)
{
$oldStory = $oldStories[$storyID];
+ if($oldStory->status == 'closed')
+ {
+ $ignoreStories .= "#{$storyID},";
+ continue;
+ }
if($assignedTo == $oldStory->assignedTo) continue;
$story = new stdclass();
@@ -2344,6 +2650,13 @@ class storyModel extends model
$allChanges[$storyID] = common::createChanges($oldStory, $story);
}
+
+ if($ignoreStories)
+ {
+ $ignoreStories = trim($ignoreStories, ',');
+ echo js::alert(sprintf($this->lang->story->ignoreClosedStory, $ignoreStories));
+ }
+
return $allChanges;
}
@@ -2381,14 +2694,28 @@ class storyModel extends model
$story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->activate['id'], $this->post->uid);
$this->dao->update(TABLE_STORY)->data($story)->autoCheck()->checkFlow()->where('id')->eq($storyID)->exec();
- if($story->status == 'active') $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->exec();
+ if($story->status == 'active')
+ {
+ $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->exec();
+
+ /* Sync twins. */
+ if(!empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($twinID)->exec();
+ }
+ }
+ }
$this->setStage($storyID);
/* Update parent story status. */
if($oldStory->parent > 0) $this->updateParentStatus($storyID, $oldStory->parent);
- return common::createChanges($oldStory, $story);
+ $changes = common::createChanges($oldStory, $story);
+ if(!empty($oldStory->twins)) $this->syncTwins($storyID, $oldStory->twins, $changes, 'Activated');
+ return $changes;
}
/**
@@ -3478,9 +3805,10 @@ class storyModel extends model
->andWhere('parent')->le(0)
->andWhere('type')->eq('story')
->andWhere('stage')->eq('wait')
- ->andWhere('status')->notin('closed,draft')
+ ->andWhere('status')->eq('active')
->andWhere('product')->eq($productID)
->andWhere('plan')->in('0,')
+ ->andWhere('twins')->eq('')
->beginIF($append)->orWhere('id')->in($append)->fi()
->fetchPairs();
return array(0 => '') + $stories ;
@@ -4220,6 +4548,7 @@ class storyModel extends model
if($action == 'activate') return $story->status == 'closed';
if($action == 'assignto') return $story->status != 'closed';
if($action == 'batchcreate' and $story->parent > 0) return false;
+ if($action == 'batchcreate' and !empty($story->twins)) return false;
if($action == 'batchcreate' and $story->type == 'requirement' and $story->status != 'closed') return strpos('draft,reviewing,changing', $story->status) === false;
if($action == 'batchcreate' and $config->vision == 'lite' and ($story->status == 'active' and ($story->stage == 'wait' or $story->stage == 'projected'))) return true;
/* Adjust code, hide split entry. */
@@ -4324,6 +4653,10 @@ class storyModel extends model
{
$title = $this->lang->story->subDivideTip['subStory'];
}
+ elseif(!empty($story->twins))
+ {
+ $title = $this->lang->story->subDivideTip['twinsSplit'];
+ }
else
{
if($story->status != 'active') $title = $this->lang->story->subDivideTip['notActive'];
@@ -4494,7 +4827,7 @@ class storyModel extends model
$executionID = empty($execution) ? 0 : $execution->id;
/* Adjust code, hide split entry. */
- if(common::hasPriv('story', 'batchCreate') and !$execution->multiple and !$execution->hasProduct and false)
+ if(common::hasPriv('story', 'batchCreate') and !$execution->multiple and !$execution->hasProduct)
{
$isClick = $this->isClickable($story, 'batchcreate');
$title = $this->lang->story->subdivide;
@@ -4780,6 +5113,11 @@ class storyModel extends model
$title = isset($story->planTitle) ? $story->planTitle : '';
$class .= ' text-ellipsis';
}
+ elseif($id == 'branch')
+ {
+ $title = zget($branches, $story->branch, '');
+ $class .= ' text-ellipsis';
+ }
elseif($id == 'sourceNote')
{
$title = $story->sourceNote;
@@ -5748,6 +6086,16 @@ class storyModel extends model
/* Delete versions that is after this version. */
$this->dao->delete()->from(TABLE_STORYSPEC)->where('story')->eq($story->id)->andWHere('version')->in($oldStory->version)->exec();
$this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($story->id)->andWhere('version')->in($oldStory->version)->exec();
+
+ /* Sync twins. */
+ if(!empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $this->dao->delete()->from(TABLE_STORYSPEC)->where('story')->eq($twinID)->andWHere('version')->in($oldStory->version)->exec();
+ $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($twinID)->andWhere('version')->in($oldStory->version)->exec();
+ }
+ }
}
if($result == 'reject')
@@ -5853,6 +6201,19 @@ class storyModel extends model
->andWhere('result')->eq('')
->exec();
+ /* Sync twins. */
+ if(!empty($oldStory->twins))
+ {
+ foreach(explode(',', trim($oldStory->twins, ',')) as $twinID)
+ {
+ $this->dao->delete()->from(TABLE_STORYREVIEW)
+ ->where('story')->eq($twinID)
+ ->andWhere('version')->eq($oldStory->version)
+ ->andWhere('result')->eq('')
+ ->exec();
+ }
+ }
+
return $story;
}
@@ -5980,7 +6341,16 @@ class storyModel extends model
/* Get related objects title or names. */
$relatedSpecs = $this->dao->select('*')->from(TABLE_STORYSPEC)->where('`story`')->in($storyIdList)->orderBy('version desc')->fetchGroup('story');
+
+ $fileIdList = array();
+ foreach($relatedSpecs as $relatedSpec)
+ {
+ if(!empty($relatedSpec[0]->files)) $fileIdList[] = $relatedSpec[0]->files;
+ }
+ $fileIdList = array_unique($fileIdList);
$relatedFiles = $this->dao->select('id, objectID, pathname, title')->from(TABLE_FILE)->where('objectType')->eq('story')->andWhere('objectID')->in($storyIdList)->andWhere('extra')->ne('editor')->fetchGroup('objectID');
+ $filesInfo = $this->dao->select('id, objectID, pathname, title')->from(TABLE_FILE)->where('id')->in($fileIdList)->andWhere('extra')->ne('editor')->fetchAll('id');
+
foreach($stories as $story)
{
$story->spec = '';
@@ -5991,6 +6361,11 @@ class storyModel extends model
$story->title = $storySpec->title;
$story->spec = $storySpec->spec;
$story->verify = $storySpec->verify;
+
+ if(!empty($storySpec->files) and empty($relatedFiles[$story->id]) and !empty($filesInfo[$storySpec->files]))
+ {
+ $relatedFiles[$story->id][0] = $filesInfo[$storySpec->files];
+ }
}
if($this->post->fileType == 'csv')
@@ -6079,22 +6454,33 @@ class storyModel extends model
* Get the story status after activation.
*
* @param int $storyID
+ * @param bool $hasTwins
* @access public
* @return void
*/
- public function getActivateStatus($storyID)
+ public function getActivateStatus($storyID, $hasTwins = true)
{
$status = 'active';
+ $action = $hasTwins ? 'closed,reviewrejected,synctwins' : 'closed,reviewrejected';
$lastRecord = $this->dao->select('action,extra')->from(TABLE_ACTION)
->where('objectType')->eq('story')
->andWhere('objectID')->eq($storyID)
- ->andWhere('action')->in('closed,reviewrejected')
+ ->andWhere('action')->in($action)
->orderBy('id_desc')
->fetch();
$lastAction = $lastRecord->action;
- if($lastAction == 'reviewrejected') $status = 'draft';
- if($lastAction == 'closed') $status = strpos($lastRecord->extra, '|') !== false ? substr($lastRecord->extra, strpos($lastRecord->extra, '|') + 1) : 'active';
+ if(strpos('closed,reviewrejected', $lastAction) !== false)
+ {
+ $status = strpos($lastRecord->extra, '|') !== false ? substr($lastRecord->extra, strpos($lastRecord->extra, '|') + 1) : 'active';
+ }
+
+ /* When activating twin story, you need to check the status of the twin story selected when closing. */
+ if($lastAction == 'synctwins')
+ {
+ $syncStoryID = strpos($lastRecord->extra, '|') !== false ? substr($lastRecord->extra, strpos($lastRecord->extra, '|') + 1) : 0;
+ $status = $this->getActivateStatus($syncStoryID, false);
+ }
return $status;
}
@@ -6133,4 +6519,47 @@ class storyModel extends model
return $lastReviewer;
}
+
+ /**
+ * Sync twins.
+ *
+ * @param int $storyID
+ * @param string $twins
+ * @param array $changes
+ * @param string $operate
+ * @access public
+ * @return void
+ */
+ public function syncTwins($storyID, $twins, $changes, $operate)
+ {
+ if(empty($twins) or empty($changes)) return;
+
+ /* Get the fields and values to be synchronized. */
+ $syncFieldList = array();
+ foreach($changes as $changeInfo)
+ {
+ $fieldName = $changeInfo['field'];
+ $fieldValue = $changeInfo['new'];
+
+ if(strpos('product,branch,module,plan,spec,verify,files,reviewers', $fieldName) !== false) continue;
+ $syncFieldList[$fieldName] = $fieldValue;
+ }
+
+ if(empty($syncFieldList)) return;
+
+ /* Synchronize and record dynamics. */
+ $this->loadModel('action');
+ $twins = explode(',', trim($twins, ','));
+ foreach($twins as $twinID)
+ {
+ $this->dao->update(TABLE_STORY)->data($syncFieldList)->where('id')->eq((int)$twinID)->exec();
+ if(!dao::isError())
+ {
+ $this->setStage($twinID);
+
+ $actionID = $this->action->create('story', $twinID, 'synctwins', '', "$operate|$storyID");
+ $this->action->logHistory($actionID, $changes);
+ }
+ }
+ }
}
diff --git a/module/story/view/activate.html.php b/module/story/view/activate.html.php
index e1876c9dfa..aaf644bc02 100644
--- a/module/story/view/activate.html.php
+++ b/module/story/view/activate.html.php
@@ -28,6 +28,12 @@
story->assignedTo;?>
closedBy, 'class="form-control chosen"');?>
+ twins)):?>
+
+
+ story->activateSyncTip;?>
+
+
printExtendFields($story, 'table');?>
story->comment;?>
diff --git a/module/story/view/affected.html.php b/module/story/view/affected.html.php
index b47ed04ea6..aafa1adf88 100644
--- a/module/story/view/affected.html.php
+++ b/module/story/view/affected.html.php
@@ -1,8 +1,17 @@
+story->currentBranch = sprintf($this->lang->story->currentBranch, $this->lang->product->branchName[$product->type]);
+ unset($twins[$story->id]);
+}?>
+
+
+
+
+
+ idAB;?>
+ story->branch;?>
+ story->title;?>
+ statusAB;?>
+ story->stageAB;?>
+ story->openedBy;?>
+ story->lastEditedByAB;?>
+
+
+
+
+ branch]) ? $branches[$twin->branch] : '';
+ $labelClass = $story->branch == $twin->branch ? 'label-primary' : '';
+ ?>
+
+ id;?>
+
+ createLink('story', 'view', "id=$twin->id", '', true), "$twin->title", '', "class='$twinsClass viewlink'") : "$twin->title");?>
+ processStatus('story', $twin);?>
+ story->stageList[$twin->stage];?>
+ openedBy);?>
+ lastEditedBy);?>
+
+
+
+
+
+
diff --git a/module/story/view/assignto.html.php b/module/story/view/assignto.html.php
index 8db1b45f73..1b7c82a191 100644
--- a/module/story/view/assignto.html.php
+++ b/module/story/view/assignto.html.php
@@ -28,7 +28,13 @@
story->assign;?>
assignedTo, "class='form-control chosen'");?>
-
+
+ twins)):?>
+
+
+ story->assignSyncTip;?>
+
+
story->status;?>
status);?>
diff --git a/module/story/view/batchclose.html.php b/module/story/view/batchclose.html.php
index 21cf183703..41148cf959 100755
--- a/module/story/view/batchclose.html.php
+++ b/module/story/view/batchclose.html.php
@@ -34,7 +34,12 @@
$story):?>
- title;?>
+
+ title;?>
+ twins)):?>
+ story->twins}:"?>
+
+
processStatus('story', $story);?>
status == 'draft') unset($reasonList['cancel']);?>
diff --git a/module/story/view/blocktwins.html.php b/module/story/view/blocktwins.html.php
new file mode 100644
index 0000000000..7b4764ec33
--- /dev/null
+++ b/module/story/view/blocktwins.html.php
@@ -0,0 +1,29 @@
+story->currentBranch = sprintf($this->lang->story->currentBranch, $this->lang->product->branchName[$product->type]);
+js::set('relieveURL', inlink('ajaxRelieveTwins'));
+js::set('storyID', $story->id);
+js::set('relieved', $lang->story->relieved);
+js::set('relievedTip', $lang->story->relievedTip);
+js::set('cancel', $lang->cancel);
+$canViewLinkStory = common::hasPriv('story', 'view');
+$canRelieved = common::hasPriv('story', 'relieved');
+array_unshift($twins, $story);
+$class = isonlybody() ? 'showinonlybody' : 'iframe';
+
+foreach($twins as $twin)
+{
+ $id = $twin->id;
+ $title = '#' . $id . ' '. $twin->title;
+ $branch = isset($branches[$twin->branch]) ? $branches[$twin->branch] : '';
+ $stage = $lang->story->stageList[$twin->stage];
+ $labelClass = $story->branch == $twin->branch ? 'label-primary' : '';
+ $hide = $this->app->rawMethod == 'edit' ? '' : 'hide';
+
+ echo "";
+ echo "{$branch} ";
+ echo "{$stage} ";
+ echo ($canViewLinkStory ? html::a($this->createLink('story', 'view', "id=$id", '', true), "$title", '', "class='$class viewlink' data-width='80%'") : "$title");
+ if($canRelieved) echo " ";
+ echo " ";
+}
+?>
diff --git a/module/story/view/change.html.php b/module/story/view/change.html.php
index c4fc435056..82208e44f7 100644
--- a/module/story/view/change.html.php
+++ b/module/story/view/change.html.php
@@ -44,14 +44,24 @@
story->title;?>
-
-
-
-
+ twins) ? '' : 'title-group';?>
+
- title, 'class="form-control story-title"');?>
diff --git a/module/story/view/close.html.php b/module/story/view/close.html.php
index 8f09e2a651..9b927c4b6c 100644
--- a/module/story/view/close.html.php
+++ b/module/story/view/close.html.php
@@ -26,8 +26,17 @@
story->closedReason;?>
-
-
+
+
+
+
+
+ story->relievedTwinsRelation;?>
+
+
+
+
+
story->duplicateStory;?>
'') + $productStories, '', "class='form-control' placeholder='{$lang->bug->duplicateTip}'"); ?>
diff --git a/module/story/view/create.html.php b/module/story/view/create.html.php
index 3f92c9a6f5..e7db743c1d 100644
--- a/module/story/view/create.html.php
+++ b/module/story/view/create.html.php
@@ -17,6 +17,9 @@
story->feedbackSource); ?>
story->create->requiredFields);?>
+
+
+
story->create->requiredFields) as $field)
{
@@ -51,35 +54,78 @@ foreach(explode(',', $config->story->create->requiredFields) as $field)
story->module : $lang->story->product;?>
-
+
-
+
-
- ';
- }
- ?>
+ switchBranch'>
+
+
+ switchBranch'>
+ switchBranch'>
+ ';
+ }
+ ?>
-
- story->assignedTo : $lang->story->planAB;?>
-
+ switchBranch' >
+ product->branchName[$product->type];?>
+
+
+
+
+
+
+
+
+
+
+ story->notice->branch;?>
+
+
+
+ story->planAB;?>
+
story->create->requiredFields) as $field)
config->URAndSR):?>
story->parent : $lang->story->requirement;?>
-
+
+
+
+ story->loadAllStories, "class='btn btn-default' onclick='loadURS(true)' data-toggle='tooltip'");?>
+
+
>
@@ -311,10 +362,53 @@ foreach(explode(',', $config->story->create->requiredFields) as $field)
+
+
+
story->module);?>
+story->plan);?>
+
diff --git a/module/story/view/edit.html.php b/module/story/view/edit.html.php
index a201e015ef..c7b82a1194 100644
--- a/module/story/view/edit.html.php
+++ b/module/story/view/edit.html.php
@@ -24,6 +24,8 @@
story->feedbackSource); ?>
status);?>
+twins)?>
+story->relievedTwinsTip)?>
-
- task->mode;?>
- task->modeList, $task->mode, "class='form-control chosen'");?>
-
type == 'kanban'):?>
kanbancard->region;?>
diff --git a/module/task/view/edit.html.php b/module/task/view/edit.html.php
index be0c12c53d..54763f1ee8 100644
--- a/module/task/view/edit.html.php
+++ b/module/task/view/edit.html.php
@@ -22,8 +22,8 @@
user->account);?>
members);?>
+
task->confirmChangeExecution);?>
-
task->error->teamMember);?>
lang->task->error->leftEmptyAB, $this->lang->task->statusList[$task->status]));?>
error->gt, $lang->task->estimate, '0'))?>
@@ -52,25 +52,15 @@ foreach(explode(',', $config->task->edit->requiredFields) as $field)
task->name;?>
+
+ task->mode;?>
+
+ status == 'wait')
+ {
+ echo html::select('mode', $lang->task->editModeList, $task->mode, "class='form-control chosen'");
+ }
+ else
+ {
+ if($task->mode == '')
+ {
+ echo $lang->task->editModeList['single'];
+ }
+ else
+ {
+ echo zget($lang->task->editModeList, $task->mode);
+ }
+ }
+ ?>
+
+
task->assignedTo;?>
team) and $task->mode == 'linear') ? "disabled='disabled'" : '';?>
@@ -159,15 +171,13 @@ foreach(explode(',', $config->task->edit->requiredFields) as $field)
$taskMembers = $members;
}
?>
- assignedTo, "class='form-control chosen' {$disableAssignedTo}");?>
-
-
- task->mode;?>
- task->modeList, $task->mode) . html::hidden('mode', $task->mode);?>
-
-
' id='teamTr'>
- task->team;?>
- task->team, '', "class='form-control btn' data-toggle='modal'");?>
+
+
+
task->type;?>
diff --git a/module/task/view/taskteam.html.php b/module/task/view/taskteam.html.php
index 9a12387608..0df362ab2b 100644
--- a/module/task/view/taskteam.html.php
+++ b/module/task/view/taskteam.html.php
@@ -8,7 +8,6 @@
rawMethod != 'create'):?>
rawMethod == 'assignto'):?>
@@ -145,19 +144,17 @@ $(document).ready(function()
showTeamMenu();
+ mode) and $task->mode == 'multi'):?>
$('tr.teamTemplate').closest('tbody.sortable').sortable('destroy');
-
- mode) or $task->mode != 'multi'):?>
- var canSort = false;
+
var options = {
- trigger: '.icon-move',
- selector: 'tr.teamTemplate',
+ selector: '.icon-move',
dragCssClass: 'drag-row',
reverse: true,
finish: setLineNumber
}
- $('#taskTeamEditor > .sortable').sortable(options);
+ $('#taskTeamEditor tbody.sortable').sortable(options);
/* Init task team manage dialog */
@@ -341,7 +338,7 @@ $(document).ready(function()
$(this).find('option:disabled').removeAttr('disabled').trigger("chosen:updated");
})
}
- if($('#teamMember').val() != '') $taskTeamEditor.find('tfoot .btn').click();
+ if($('#teamMember').val() != '' && page != 'edit') $taskTeamEditor.find('tfoot .btn').click();
})
});
diff --git a/module/task/view/view.html.php b/module/task/view/view.html.php
index f7ee9236a9..02602474db 100644
--- a/module/task/view/view.html.php
+++ b/module/task/view/view.html.php
@@ -24,7 +24,7 @@
id?>
- team)) echo '' . $lang->task->multipleAB . (common::checkNotCN() ? ' ' : '') . $lang->task->modeList[$task->mode] . ' ';?>
+ team)) echo '' . (common::checkNotCN() ? ' ' : '') . $lang->task->modeList[$task->mode] . ' ';?>
parent > 0) echo '' . $lang->task->childrenAB . ' ';?>
parent > 0) echo isset($task->parentName) ? html::a(inlink('view', "taskID={$task->parent}"), $task->parentName) . ' / ' : '';?>name;?>
@@ -339,9 +339,9 @@
task->team?>
- task->estimate?>
- task->consumed?>
- task->left?>
+ task->estimateAB?>
+ task->consumedAB?>
+ task->leftAB?>
statusAB;?>
diff --git a/module/testcase/control.php b/module/testcase/control.php
index d6f9cbf31b..ed3b5eac85 100644
--- a/module/testcase/control.php
+++ b/module/testcase/control.php
@@ -535,8 +535,6 @@ class testcase extends control
$currentModuleID = $moduleID ? (int)$moduleID : (int)$this->cookie->lastCaseModule;
/* Get the status of stories are not closed. */
- $storyStatus = $this->lang->story->statusList;
- unset($storyStatus['closed']);
$modules = array();
if($currentModuleID)
{
@@ -546,7 +544,7 @@ class testcase extends control
$modules = $this->tree->getAllChildID($modules);
}
- $stories = $this->story->getProductStoryPairs($productID, $branch, $modules, array_keys($storyStatus), 'id_desc', 50, 'null', 'story', false);
+ $stories = $this->story->getProductStoryPairs($productID, $branch, $modules, 'active', 'id_desc', 50, 'null', 'story', false);
if($this->app->tab != 'qa' and $this->app->tab != 'product')
{
$projectID = $this->app->tab == 'project' ? $this->session->project : $this->session->execution;
diff --git a/module/testcase/lang/zh-cn.php b/module/testcase/lang/zh-cn.php
index 45b5045f3c..12b852d88f 100644
--- a/module/testcase/lang/zh-cn.php
+++ b/module/testcase/lang/zh-cn.php
@@ -20,7 +20,7 @@ $lang->testcase->howRun = '测试方式';
$lang->testcase->frequency = '使用频率';
$lang->testcase->path = '路径';
$lang->testcase->lib = "所属库";
-$lang->testcase->branch = "分支/平台";
+$lang->testcase->branch = "平台/分支";
$lang->testcase->moduleAB = '模块';
$lang->testcase->story = "相关{$lang->SRCommon}";
$lang->testcase->storyVersion = "{$lang->SRCommon}版本";
@@ -186,7 +186,7 @@ $lang->testcase->confirmDelete = '您确认要删除该测试用例吗
$lang->testcase->confirmBatchDelete = '您确认要批量删除这些测试用例吗?';
$lang->testcase->ditto = '同上';
$lang->testcase->dittoNotice = '该用例与上一用例不属于同一产品!';
-$lang->testcase->confirmUnlinkTesttask = '用例[%s]已关联在之前所属分支/平台的测试单中,调整分支/平台后,将从之前所属分支/平台的测试单中移除,请确认是否继续修改。';
+$lang->testcase->confirmUnlinkTesttask = '用例[%s]已关联在之前所属平台/分支的测试单中,调整平台/分支后,将从之前所属平台/分支的测试单中移除,请确认是否继续修改。';
$lang->testcase->reviewList[0] = '否';
$lang->testcase->reviewList[1] = '是';
diff --git a/module/testcase/model.php b/module/testcase/model.php
index e614d330df..29742064ef 100644
--- a/module/testcase/model.php
+++ b/module/testcase/model.php
@@ -1603,9 +1603,9 @@ class testcaseModel extends model
{
$caseID = $this->dao->lastInsertID();
$parentStepID = 0;
- if($this->post->desc)
+ if($data->desc)
{
- foreach($this->post->desc[$key] as $id => $desc)
+ foreach($data->desc[$key] as $id => $desc)
{
$desc = trim($desc);
if(empty($desc)) continue;
@@ -1615,7 +1615,7 @@ class testcaseModel extends model
$stepData->case = $caseID;
$stepData->version = 1;
$stepData->desc = htmlSpecialString($desc);
- $stepData->expect = htmlSpecialString($this->post->expect[$key][$id]);
+ $stepData->expect = htmlSpecialString($data->expect[$key][$id]);
$this->dao->insert(TABLE_CASESTEP)->data($stepData)->autoCheck()->exec();
if($stepData->type == 'group') $parentStepID = $this->dao->lastInsertID();
if($stepData->type == 'step') $parentStepID = 0;
diff --git a/module/testcase/view/view.html.php b/module/testcase/view/view.html.php
index 979324e339..716a59ed69 100644
--- a/module/testcase/view/view.html.php
+++ b/module/testcase/view/view.html.php
@@ -176,7 +176,7 @@
foreach($modulePath as $key => $module)
{
- if($tab == 'qa' || $tab == 'ops')
+ if($tab == 'qa' || $tab == 'ops' || $tab == 'product')
{
if($isLibCase)
{
diff --git a/module/testsuite/control.php b/module/testsuite/control.php
index 98b4023dc1..87254e67b7 100644
--- a/module/testsuite/control.php
+++ b/module/testsuite/control.php
@@ -89,7 +89,7 @@ class testsuite extends control
$privateNum++;
}
}
- $suitesNum = !empty(count($suites, 0)) ? count($suites, 0) : 0;
+ $suitesNum = !empty($suites) ? count($suites) : 0;
$publicNum = $suitesNum - $privateNum;
$summary = str_replace(array('%total%', '%public%', '%private%'), array($suitesNum, $publicNum, $privateNum), $this->lang->testsuite->summary);
diff --git a/module/testtask/control.php b/module/testtask/control.php
index 0580f2491c..39260011f4 100644
--- a/module/testtask/control.php
+++ b/module/testtask/control.php
@@ -250,7 +250,7 @@ class testtask extends control
/* Create testtask from testtask of test.*/
$productID = $productID ? $productID : key($this->products);
$executions = empty($productID) ? array() : $this->loadModel('product')->getExecutionPairsByProduct($productID, '', 'id_desc', $projectID, 'stagefilter');
- $builds = empty($productID) ? array() : $this->loadModel('build')->getBuildPairs($productID, 'all', 'notrunk,withexecution', $projectID, 'project');
+ $builds = empty($productID) ? array() : $this->loadModel('build')->getBuildPairs($productID, 'all', 'notrunk,withexecution', $projectID, 'project', '', false);
$execution = $this->loadModel('execution')->getByID($executionID);
if(!empty($execution) and $execution->type == 'kanban') $this->lang->testtask->execution = str_replace($this->lang->execution->common, $this->lang->kanban->common, $this->lang->testtask->execution);
@@ -816,11 +816,11 @@ class testtask extends control
$executions[$executionID] = $project->name . "({$this->lang->project->disableExecution})";
}
}
- $builds = $this->loadModel('build')->getBuildPairs($productID, 'all', 'noempty,notrunk,withexecution', $executionID, 'execution');
+ $builds = $this->loadModel('build')->getBuildPairs($productID, 'all', 'noempty,notrunk,withexecution', $executionID, 'execution', $task->build, false);
}
else
{
- $builds = $this->loadModel('build')->getBuildPairs($productID, 'all', 'noempty,notrunk,withexecution', $task->project, 'project');
+ $builds = $this->loadModel('build')->getBuildPairs($productID, 'all', 'noempty,notrunk,withexecution', $task->project, 'project', $task->build, false);
}
$this->view->title = $this->products[$productID] . $this->lang->colon . $this->lang->testtask->edit;
diff --git a/module/testtask/model.php b/module/testtask/model.php
index e8f23dd989..5677fbcd52 100755
--- a/module/testtask/model.php
+++ b/module/testtask/model.php
@@ -91,7 +91,7 @@ class testtaskModel extends model
->beginIF($scopeAndStatus[0] == 'all')->andWhere('t1.product')->in($products)->fi()
->beginIF(strtolower($scopeAndStatus[1]) == 'totalstatus')->andWhere('t1.status')->in('blocked,doing,wait,done')->fi()
->beginIF(!in_array(strtolower($scopeAndStatus[1]), array('totalstatus', 'review'), true))->andWhere('t1.status')->eq($scopeAndStatus[1])->fi()
- ->beginIF($branch !== 'all')->andWhere('t4.branch')->eq($branch)->fi()
+ ->beginIF($branch !== 'all')->andWhere("CONCAT(',', t4.branch, ',')")->like("%,$branch,%")->fi()
->beginIF($beginTime)->andWhere('t1.begin')->ge($beginTime)->fi()
->beginIF($endTime)->andWhere('t1.end')->le($endTime)->fi()
->beginIF($branch == BRANCH_MAIN)
diff --git a/module/todo/control.php b/module/todo/control.php
index 173526d4f4..b8b41150a8 100644
--- a/module/todo/control.php
+++ b/module/todo/control.php
@@ -668,7 +668,7 @@ class todo extends control
* AJAX: get program id.
*
* @param int $objectID
- * @param varchar $objectType
+ * @param string $objectType
* @access public
* @return void
*/
diff --git a/module/transfer/config.php b/module/transfer/config.php
index 35deafb6c1..a97d8f24f9 100644
--- a/module/transfer/config.php
+++ b/module/transfer/config.php
@@ -12,12 +12,13 @@ $config->transfer->fieldList['sort'] = '';
$config->transfer->fieldList['dataSource'] = array('module' => '', 'method' => '', 'params' => '', 'pairs' => '', 'lang' => '');
$config->transfer->initFunction = 'title,control,required,';
-$config->transfer->dateFeilds = 'estStarted,realStarted,deadline,openedDate,assignedDate,finishedDate,canceledDate,closedDate,lastEditedDate,';
-$config->transfer->datetimeFeilds = '';
+$config->transfer->dateFields = 'estStarted,realStarted,deadline,openedDate,assignedDate,finishedDate,canceledDate,closedDate,lastEditedDate,';
+$config->transfer->datetimeFields = '';
$config->transfer->listFields = '';
$config->transfer->sysLangFields = ',pri,status,type,mode,severity,os,browser,resolution,confirmed,source,reviewResult,stage,change,category';
$config->transfer->sysDataFields = 'execution,product,user';
$config->transfer->userFields = 'assignedTo,openedBy,finishedBy,canceledBy,closedBy,lastEditedBy,lastRunner,resolvedBy,reviewedBy,mailto';
+$config->transfer->textareaFields = 'spec,desc';
$config->transfer->defaultZeroField = 'severity,pri';
$config->transfer->defaultEmptyField = '';
diff --git a/module/transfer/control.php b/module/transfer/control.php
index b9b7851107..65d13bbc52 100755
--- a/module/transfer/control.php
+++ b/module/transfer/control.php
@@ -163,13 +163,17 @@ class transfer extends control
if($product->type == 'normal') unset($fields['branch']);
if($this->session->storyType == 'requirement') unset($fields['plan']);
}
-
if($model == 'bug')
{
$product = $this->loadModel('product')->getByID($this->session->bugTransferParams['productID']);
if($product->type == 'normal') unset($fields['branch']);
if($product->shadow and ($this->app->tab == 'execution' or $this->app->tab == 'project')) unset($fields['product']);
}
+ if($model == 'testcase')
+ {
+ $product = $this->loadModel('product')->getByID($this->session->testcaseTransferParams['productID']);
+ if($product->type == 'normal') unset($fields['branch']);
+ }
if($model == 'task') $datas = $this->task->processDatas4Task($datas);
$html = $this->transfer->buildNextList($datas->datas, $lastID, $fields, $pagerID, $model);
die($html);
diff --git a/module/transfer/model.php b/module/transfer/model.php
index 8da0aea25f..f9374a6bb8 100644
--- a/module/transfer/model.php
+++ b/module/transfer/model.php
@@ -506,10 +506,32 @@ class transferModel extends model
{
$modelData = $this->getDatasByFile($tmpFile);
}
+
+ $modelData = $this->processDate($modelData);
if(isset($fields['id'])) unset($fields['id']);
return $modelData;
}
+ /**
+ * Process datas, convert date to YYYY-mm-dd, convert datetime to YYYY-mm-dd HH:ii:ss.
+ *
+ * @param array $datas
+ * @access public
+ * @return array
+ */
+ public function processDate($datas)
+ {
+ foreach($datas as $index => $data)
+ {
+ foreach($data as $field => $value)
+ {
+ if(strpos($this->modelConfig->dateFields, $field) !== false or strpos($this->modelConfig->datetimeFields, $field) !== false) $data->$field = $this->loadModel('common')->formatDate($value);
+ }
+ $datas[$index] = $data;
+ }
+ return $datas;
+ }
+
/**
* Get rows from excel.
*
@@ -830,7 +852,7 @@ class transferModel extends model
$storyDatas = end($stories);
$lastBranch = $storyDatas->branch;
$lastType = $storyDatas->type;
- $stories = $this->story->mergePlanTitle($productID , $stories, $lastBranch, $lastType);
+ $stories = $this->loadModel('story')->mergePlanTitle($productID , $stories, $lastBranch, $lastType);
return $stories;
}
@@ -940,7 +962,7 @@ class transferModel extends model
foreach($data as $field => $cellValue)
{
if(empty($cellValue)) continue;
- if(strpos($this->transferConfig->dateFeilds, $field) !== false and helper::isZeroDate($cellValue)) $datas[$key]->$field = '';
+ if(strpos($this->transferConfig->dateFields, $field) !== false and helper::isZeroDate($cellValue)) $datas[$key]->$field = '';
if(is_array($cellValue)) continue;
if(!empty($fieldList[$field]['from']) and in_array($fieldList[$field]['control'], array('select', 'multiple')))
@@ -1325,8 +1347,8 @@ class transferModel extends model
if(!isset($modelConfig->export)) $modelConfig->export = new stdClass();
if(!isset($modelConfig->import)) $modelConfig->export = new stdClass();
- $modelConfig->dateFeilds = isset($modelConfig->dateFeilds) ? $modelConfig->dateFeilds : $transferConfig->dateFeilds;
- $modelConfig->datetimeFeilds = isset($modelConfig->datetimeFeilds) ? $modelConfig->datetimeFeilds : $transferConfig->datetimeFeilds;
+ $modelConfig->dateFields = isset($modelConfig->dateFields) ? $modelConfig->dateFields : $transferConfig->dateFields;
+ $modelConfig->datetimeFields = isset($modelConfig->datetimeFields) ? $modelConfig->datetimeFields : $transferConfig->datetimeFields;
$modelConfig->sysLangFields = isset($modelConfig->sysLangFields) ? $modelConfig->sysLangFields : $transferConfig->sysLangFields;
$modelConfig->sysDataFields = isset($modelConfig->sysDataFields) ? $modelConfig->sysDataFields : $transferConfig->sysDataFields;
$modelConfig->listFields = isset($modelConfig->listFields) ? $modelConfig->listFields : $transferConfig->listFields;
@@ -1447,7 +1469,7 @@ class transferModel extends model
if($model == 'bug' and $field == 'steps') $selected = str_replace("\n\n\n\n\n\n", '', $selected);
$html .= '
' . html::textarea("$name", $selected, "class='form-control' cols='50' rows='1'") . ' ';
}
- elseif($field == 'stepDesc' or $field == 'stepExpect')
+ elseif($field == 'stepDesc' or $field == 'stepExpect' or $field == 'precondition')
{
$stepDesc = $this->process4Testcase($field, $tmpList, $row);
if($stepDesc) $html .= '
' . $stepDesc . ' ';
@@ -1473,6 +1495,7 @@ class transferModel extends model
}
$html .= '';
}
+ elseif(strpos($this->transferConfig->textareaFields, $field) !== false) $html .= '
' . html::textarea("$name", $selected, "class='form-control' style='overflow:hidden;'") . ' ';
else $html .= '
' . html::input("$name", $selected, "class='form-control autocomplete='off'") . ' ';
}
@@ -1484,7 +1507,7 @@ class transferModel extends model
}
/**
- * Process stepExpect and stepDesc for testcase.
+ * Process stepExpect、stepDesc and precondition for testcase.
*
* @param int $field
* @param int $datas
@@ -1497,6 +1520,7 @@ class transferModel extends model
$stepData = $this->testcase->processDatas($datas);
$html = '';
+ if($field == 'precondition' and isset($datas[$key])) return html::textarea("precondition[$key]", htmlSpecialString($datas[$key]->precondition), "class='form-control' style='overflow:hidden'");
if($field == 'stepExpect') return $html;
if(isset($stepData[$key]['desc']))
{
diff --git a/module/tree/control.php b/module/tree/control.php
index 1041211554..dc5c7d5285 100644
--- a/module/tree/control.php
+++ b/module/tree/control.php
@@ -390,7 +390,7 @@ class tree extends control
}
else
{
- $this->view->optionMenu = $this->tree->getOptionMenu($module->root, $module->type, 0, $module->branch);
+ $this->view->optionMenu = $this->tree->getOptionMenu($module->root, $module->type, 0, $module->branch, 'noMainBranch');
}
if($type == 'doc') $this->view->libs = $this->loadModel('doc')->getLibs('all', $extra = 'withObject', '', 0, 'book');
@@ -534,8 +534,17 @@ class tree extends control
}
else
{
- $optionMenu = $this->tree->getOptionMenu($rootID, $viewType, $rootModuleID, $branch);
+ $optionMenu = $this->tree->getOptionMenu($rootID, $viewType, $rootModuleID, $branch, $extra);
}
+
+ if(strpos($extra, 'excludeModuleID') !== false)
+ {
+ list($excludeModule, $noMainBranch) = explode(',', $extra);
+ parse_str($excludeModule, $output);
+ $excludeModuleID = $output['excludeModuleID'];
+ if(isset($optionMenu[$excludeModuleID])) unset($optionMenu[$excludeModuleID]);
+ }
+
if($returnType == 'html')
{
//Code for task #5081.
@@ -552,13 +561,13 @@ class tree extends control
$changeFunc = '';
if($viewType == 'bug' or $viewType == 'case') $changeFunc = "onchange='loadModuleRelated()'";
if($viewType == 'task') $changeFunc = "onchange='setStories(this.value, $rootID)'";
- $field = $fieldID ? "modules[$fieldID]" : 'module';
+ $field = $fieldID !== '' ? "modules[$fieldID]" : 'module';
$currentModule = $this->tree->getById($currentModuleID);
$currentModuleID = (isset($currentModule->branch) and $currentModule->branch == 0) ? $currentModuleID : 0;
$output = html::select("$field", $optionMenu, $currentModuleID, "class='form-control' $changeFunc");
- if(count($optionMenu) == 1 and $needManage)
+ if(count($optionMenu) == 1 and $needManage !== 'false')
{
$output .= "
";
$output .= html::a($this->createLink('tree', 'browse', "rootID=$rootID&view=$viewType¤tModuleID=0&branch=$branch", '', true), $this->lang->tree->manage, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='95%'");
diff --git a/module/tree/model.php b/module/tree/model.php
index c84f818e66..8b4da78ff1 100644
--- a/module/tree/model.php
+++ b/module/tree/model.php
@@ -109,11 +109,14 @@ class treeModel extends model
->andWhere('type')->in($type)
->beginIF($grade)->andWhere('grade')->le($grade)->fi()
->beginIF($startModulePath)->andWhere('path')->like($startModulePath)->fi()
- ->beginIF($branch !== 'all' and $branch !== '' and $branch !== false)
+ ->beginIF($branch !== 'all' and $branch !== '' and $branch !== false and strpos($param, 'noMainBranch') === false)
->andWhere('(branch')->eq(0)
->orWhere('branch')->eq($branch)
->markRight(1)
->fi()
+ ->beginIF($branch !== 'all' and $branch !== '' and $branch !== false and strpos($param, 'noMainBranch') !== false)
+ ->andWhere('branch')->eq($branch)
+ ->fi()
->beginIF(strpos($param, 'nodeleted') !== false)->andWhere('deleted')->eq(0)->fi()
->orderBy('grade desc, `order`')
->get();
@@ -162,7 +165,7 @@ class treeModel extends model
if($product and $product->type != 'normal')
{
$branchPairs = $this->loadModel('branch')->getPairs($rootID, 'all');
- $branches = array($branch => $branchPairs[$branch]);
+ foreach(explode(',', $branch) as $branchID) $branches += array($branchID => $branchPairs[$branchID]);
}
elseif($product and $product->type == 'normal')
{
@@ -175,7 +178,7 @@ class treeModel extends model
$treeMenu = array();
foreach($branches as $branchID => $branch)
- {
+ {
$stmt = $this->dbh->query($this->buildMenuQuery($rootID, $type, $startModule, $branchID, $param));
$modules = array();
while($module = $stmt->fetch())
diff --git a/module/tree/view/edit.html.php b/module/tree/view/edit.html.php
index 861740098a..09e8713b54 100644
--- a/module/tree/view/edit.html.php
+++ b/module/tree/view/edit.html.php
@@ -189,7 +189,7 @@ function loadModules(branch)
if(typeof(branchID) == 'undefined') branchID = 0;
if(typeof(moduleID) == 'undefined') moduleID = 0;
- link = createLink('tree', 'ajaxGetOptionMenu', 'productID=' + productID + '&viewtype=story&branch=' + branchID + '&rootModuleID=0&returnType=html&fieldID=&needManage=true&extra=¤tModuleID=' + moduleID);
+ link = createLink('tree', 'ajaxGetOptionMenu', 'productID=' + productID + '&viewtype=story&branch=' + branchID + '&rootModuleID=0&returnType=html&fieldID=&needManage=true&extra=excludeModuleID=' + id;?> + ',noMainBranch¤tModuleID=' + moduleID);
$(moduleBox).load(link, function()
{
$(this).children('select').attr('id', 'parent').attr('name', 'parent');
diff --git a/module/upgrade/config.php b/module/upgrade/config.php
index 79c16a3b7d..4d05a2c9cd 100644
--- a/module/upgrade/config.php
+++ b/module/upgrade/config.php
@@ -29,6 +29,7 @@ $config->upgrade->maxVersion['max3_6_3'] = '17_6_2';
$config->upgrade->maxVersion['max3_7'] = '17_7';
$config->upgrade->maxVersion['max3_8'] = '17_8';
$config->upgrade->maxVersion['max4_0_beta1'] = '18_0_beta1';
+$config->upgrade->maxVersion['max4_0_beta2'] = '18_0_beta2';
$config->upgrade->bizVersion = array();
$config->upgrade->bizVersion['biz1_0'] = '9_5_1';
@@ -96,6 +97,7 @@ $config->upgrade->bizVersion['biz7_6_2'] = '17_6_2';
$config->upgrade->bizVersion['biz7_7'] = '17_7';
$config->upgrade->bizVersion['biz7_8'] = '17_8';
$config->upgrade->bizVersion['biz8_0_beta1'] = '18_0_beta1';
+$config->upgrade->bizVersion['biz8_0_beta2'] = '18_0_beta2';
$config->upgrade->proVersion = array();
$config->upgrade->proVersion['pro1_0'] = '3_1';
diff --git a/module/upgrade/lang/version.php b/module/upgrade/lang/version.php
index 5ed3ef440e..cac5735af8 100644
--- a/module/upgrade/lang/version.php
+++ b/module/upgrade/lang/version.php
@@ -170,6 +170,7 @@ $lang->upgrade->fromVersions['17_6_2'] = '17.6.2';
$lang->upgrade->fromVersions['17_7'] = '17.7';
$lang->upgrade->fromVersions['17_8'] = '17.8';
$lang->upgrade->fromVersions['18_0_beta1'] = '18.0.beta1';
+$lang->upgrade->fromVersions['18_0_beta2'] = '18.0.beta2';
global $config;
/* Lite. */
@@ -344,6 +345,7 @@ $lang->upgrade->fromVersions['biz7_6_2'] = 'Biz7.6.2';
$lang->upgrade->fromVersions['biz7_7'] = 'Biz7.7';
$lang->upgrade->fromVersions['biz7_8'] = 'Biz7.8';
$lang->upgrade->fromVersions['biz8_0_beta1'] = 'Biz8.0.beta1';
+$lang->upgrade->fromVersions['biz8_0_beta2'] = 'Biz8.0.beta2';
/* Max. */
$lang->upgrade->fromVersions['max2_0_beta4'] = 'Max2.0.beta4';
@@ -374,3 +376,4 @@ $lang->upgrade->fromVersions['max3_6_2'] = 'Max3.6.2';
$lang->upgrade->fromVersions['max3_6_3'] = 'Max3.6.3';
$lang->upgrade->fromVersions['max3_7'] = 'Max3.7';
$lang->upgrade->fromVersions['max3_8'] = 'Max3.8';
+$lang->upgrade->fromVersions['max4_0_beta1'] = 'Max4.0.beta1';
diff --git a/module/upgrade/model.php b/module/upgrade/model.php
index 8edafef735..250fd9cfdd 100644
--- a/module/upgrade/model.php
+++ b/module/upgrade/model.php
@@ -577,6 +577,13 @@ class upgradeModel extends model
$this->xuanNotifyGroupHiddenUsers();
$this->initShadowBuilds();
break;
+ case '18_0_beta1':
+ if(!$executedXuanxuan)
+ {
+ $xuanxuanSql = $this->app->getAppRoot() . 'db' . DS . 'upgradexuanxuan6.6.sql';
+ $this->execSQL($xuanxuanSql);
+ }
+ break;
}
$this->deletePatch();
@@ -756,6 +763,9 @@ class upgradeModel extends model
case 'max3_3':
$this->addReviewIssusApprovalData();
break;
+ case 'max4_0_beta1':
+ $this->initReviewEfforts();
+ break;
}
}
@@ -1056,6 +1066,10 @@ class upgradeModel extends model
$confirmContent .= file_get_contents($this->getUpgradeFile('17.8'));
$xuanxuanSql = $this->app->getAppRoot() . 'db' . DS . 'upgradexuanxuan6.5.sql';
$confirmContent .= file_get_contents($xuanxuanSql);
+ case '18_0_beta1':
+ $confirmContent .= file_get_contents($this->getUpgradeFile('18.0.beta1'));
+ $xuanxuanSql = $this->app->getAppRoot() . 'db' . DS . 'upgradexuanxuan6.6.sql';
+ $confirmContent .= file_get_contents($xuanxuanSql);
}
return $confirmContent;
@@ -7969,4 +7983,31 @@ class upgradeModel extends model
}
return true;
}
+
+ /**
+ * Init review efforts.
+ *
+ * @access public
+ * @return void
+ */
+ public function initReviewEfforts()
+ {
+ $nodes = $this->dao->select('t1.id,t3.id as reviewID,t3.title,t2.id as approvalID,t1.extra as consumed')->from(TABLE_APPROVALNODE)->alias('t1')
+ ->leftJoin(TABLE_APPROVAL)->alias('t2')->on("t1.approval=t2.id")
+ ->leftJoin(TABLE_REVIEW)->alias('t3')->on("t2.objectID=t3.id")
+ ->where('t3.deleted')->eq('0')
+ ->andWhere('t2.deleted')->eq('0')
+ ->andWhere('t2.objectType')->eq('review')
+ ->andWhere('t1.extra')->ne('')
+ ->andWhere('t1.extra')->ne(0)
+ ->orderBy('t1.approval,t1.id')
+ ->fetchAll('id');
+ $this->loadModel('effort');
+ foreach($nodes as $node)
+ {
+ $this->dao->delete()->from(TABLE_EFFORT)->where('objectType')->eq('review')->andWhere('objectID')->eq($node->reviewID)->exec();
+ $this->effort->create('review', $node->reviewID, (int)$node->consumed, $node->title, $node->approvalID);
+ }
+ return true;
+ }
}
diff --git a/module/user/control.php b/module/user/control.php
index 081176c229..b42b793f23 100755
--- a/module/user/control.php
+++ b/module/user/control.php
@@ -1048,10 +1048,10 @@ class user extends control
public function logout($referer = 0)
{
if(isset($this->app->user->id)) $this->loadModel('action')->create('user', $this->app->user->id, 'logout');
+ setcookie('za', false, time() - 3600, $this->config->webRoot);
+ setcookie('zp', false, time() - 3600, $this->config->webRoot);
+ setcookie('tab', false, time() - 3600, $this->config->webRoot); ;
session_destroy();
- setcookie('za', false);
- setcookie('zp', false);
- setcookie('tab', false);
if($this->app->getViewType() == 'json') return print(json_encode(array('status' => 'success')));
$vars = !empty($referer) ? "referer=$referer" : '';
@@ -1090,8 +1090,7 @@ class user extends control
}
/* Remove the real path for security reason. */
- $pathPos = strrpos($this->app->getBasePath(), DIRECTORY_SEPARATOR, -2);
- $resetFileName = substr($resetFileName, $pathPos + 1);
+ $resetFileName = str_replace($this->app->getBasePath(), '', $resetFileName);
$this->view->title = $this->lang->user->resetPassword;
$this->view->status = 'reset';
diff --git a/module/user/model.php b/module/user/model.php
index b03f2abb1f..d9334f9ab2 100644
--- a/module/user/model.php
+++ b/module/user/model.php
@@ -663,6 +663,8 @@ class userModel extends model
$this->mail->syncSendCloud('sync', $user->email, $user->realname);
}
}
+
+ if($_POST['account'] == $this->app->user->account and $user->role != $_SESSION['user']->role) $_SESSION['user']->role = $user->role;
}
/**
diff --git a/module/webhook/model.php b/module/webhook/model.php
index ce5c32ad80..74a292e885 100644
--- a/module/webhook/model.php
+++ b/module/webhook/model.php
@@ -422,6 +422,7 @@ class webhookModel extends model
$viewLink = $this->getViewLink($objectType == 'kanbancard' ? 'kanban' : $objectType, $objectType == 'kanbancard' ? $object->kanban : $objectID);
$objectTypeName = ($objectType == 'story' and $object->type == 'requirement') ? $this->lang->action->objectTypes['requirement'] : $this->lang->action->objectTypes[$objectType];
$title = $this->app->user->realname . $this->lang->action->label->$actionType . $objectTypeName;
+ $host = (defined('RUN_MODE') and RUN_MODE == 'api') ? '' : $host;
$text = $title . ' ' . "[#{$objectID}::{$object->$field}](" . $host . $viewLink . ")";
$mobile = '';
diff --git a/test/class/build.class.php b/test/class/build.class.php
index 26532d486c..a51cd8ba1f 100644
--- a/test/class/build.class.php
+++ b/test/class/build.class.php
@@ -149,20 +149,6 @@ class buildTest
return $objects;
}
- /**
- * Test for filterLinked method
- *
- * @param array $buildIdList
- * @access public
- * @return string
- */
- public function filterLinkedTest($buildIdList)
- {
- $notEmptyBuilds = $this->objectModel->filterLinked($buildIdList);
- sort($notEmptyBuilds);
- return empty($notEmptyBuilds) ? 'null' : join(',', $notEmptyBuilds);
- }
-
/**
* Function getLast test by build
*
diff --git a/test/model/build/filterlinked.php b/test/model/build/filterlinked.php
deleted file mode 100644
index 4a53458aed..0000000000
--- a/test/model/build/filterlinked.php
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env php
-filterLinked();
-cid=1
-pid=1
-
-不传任何数据 >> null
-迭代版本未关联需求和Bug >> null
-迭代版本关联需求和Bug >> 101
-项目版本关联版本关联需求和Bug >> 102
-项目版本关联版本未关联需求和Bug >> null
-版本列表中关联需求和Bug >> 101
-版本列表中混合项目版本和迭代版本 >> 101,102
-
-*/
-
-$build = new buildTest();
-
-$buildData = new stdclass();
-$buildData->id = 100;
-$buildData->product = 1;
-$buildData->project = 1;
-$buildData->execution = 2;
-$buildData->name = 'test1';
-$buildData->date = date('Y-m-d');
-$build->objectModel->dao->replace(TABLE_BUILD)->data($buildData)->exec();
-$buildId1 = $buildData->id;
-
-$buildData->id = 101;
-$buildData->name = 'test2';
-$buildData->stories = '1,2,3';
-$build->objectModel->dao->replace(TABLE_BUILD)->data($buildData)->exec();
-$buildId2 = $buildData->id;
-
-$buildData = new stdclass();
-$buildData->id = 102;
-$buildData->product = 1;
-$buildData->project = 1;
-$buildData->execution = 0;
-$buildData->builds = ",$buildId2,";
-$buildData->name = 'test3';
-$buildData->date = date('Y-m-d');
-$build->objectModel->dao->replace(TABLE_BUILD)->data($buildData)->exec();
-$buildId3 = $buildData->id;
-
-$buildData = new stdclass();
-$buildData->id = 103;
-$buildData->product = 1;
-$buildData->project = 1;
-$buildData->execution = 0;
-$buildData->builds = ",$buildId1,";
-$buildData->name = 'test4';
-$buildData->date = date('Y-m-d');
-$build->objectModel->dao->replace(TABLE_BUILD)->data($buildData)->exec();
-$buildId4 = $buildData->id;
-
-
-r($build->filterLinkedTest(array())) && p() && e('null'); //不传任何数据
-r($build->filterLinkedTest(array($buildId1))) && p() && e('null'); //迭代版本未关联需求和Bug
-r($build->filterLinkedTest(array($buildId2))) && p() && e('101'); //迭代版本关联需求和Bug
-r($build->filterLinkedTest(array($buildId3))) && p() && e('102'); //项目版本关联版本关联需求和Bug
-r($build->filterLinkedTest(array($buildId4))) && p() && e('null'); //项目版本关联版本未关联需求和Bug
-r($build->filterLinkedTest(array($buildId1, $buildId2))) && p() && e('101'); //版本列表中关联需求和Bug
-r($build->filterLinkedTest(array($buildId3, $buildId2))) && p() && e('101,102'); //版本列表中混合项目版本和迭代版本
diff --git a/tools/minifyfront.php b/tools/minifyfront.php
index 4809b6daab..d60dcd93f9 100644
--- a/tools/minifyfront.php
+++ b/tools/minifyfront.php
@@ -3,7 +3,9 @@
* This file is used to compress css and js files.
*/
-$baseDir = dirname(dirname(__FILE__));
+$baseDir = dirname(dirname(__FILE__));
+include $baseDir . '/framework/helper.class.php';
+
//$miniCSSTool = getenv('MINIFY_CSS_PATH');
//$miniJSTool = getenv('MINIFY_JS_PATH');
$miniCSSTool = '/home/z/ci/minify/minifyCSS.php';
diff --git a/www/js/my.full.js b/www/js/my.full.js
index 30bf823463..ca19375a73 100644
--- a/www/js/my.full.js
+++ b/www/js/my.full.js
@@ -1044,12 +1044,7 @@ function disableSelectedProduct()
$("select[id^='products']").each(function()
{
var selectedProduct = $(this).val();
- if(selectedProduct != 0 && $.inArray(selectedProduct, selectedVal) < 0 && !multiBranchProducts[selectedProduct]) selectedVal.push(selectedProduct);
- if(multiBranchProducts[selectedProduct])
- {
- var isDisabled = checkMultiProducts(this);
- if(isDisabled) selectedVal.push(selectedProduct);
- }
+ if(selectedProduct != 0 && $.inArray(selectedProduct, selectedVal) < 0) selectedVal.push(selectedProduct);
})
$("select[id^='products']").each(function()
@@ -1208,7 +1203,7 @@ function showCheckedFields(fields)
else if(!$fieldBox.hasClass('hidden'))
{
$fieldBox.addClass('hidden');
- $field.attr('disabled', true);
+ if($(this).val() != 'branch') $field.attr('disabled', true);
}
if(config.currentModule == 'story' && $(this).val() == 'source')
diff --git a/www/theme/default/style.css b/www/theme/default/style.css
index 393e1a1e03..4938e9681c 100644
--- a/www/theme/default/style.css
+++ b/www/theme/default/style.css
@@ -219,11 +219,11 @@ tbody tr td.c-actions .dividing-line {width: 1px; height: 16px; display: inline-
.pri-text > .label-pri {padding: 1px 5px;}
.pri-text > .label-pri, .pri-text > .label-severity {height: 20px;}
-.pri-text > .label-severity {background-size: 80%; background-position: 2px -130px;}
-.pri-text > .label-severity[data-severity="1"] {color: #FB2B2B; background-position: 2px 1px;}
-.pri-text > .label-severity[data-severity="2"] {color: #FF8058; background-position: 2px -31px;}
-.pri-text > .label-severity[data-severity="3"] {color: #FAAE1A; background-position: 2px -64px;}
-.pri-text > .label-severity[data-severity="4"] {color: #B89664; background-position: 2px -97px;}
+.pri-text > .label-severity {background-size: 85%; background-position: 1px -139px;}
+.pri-text > .label-severity[data-severity="1"] {color: #FB2B2B; background-position: 2px 0px;}
+.pri-text > .label-severity[data-severity="2"] {color: #FF8058; background-position: 2px -34px;}
+.pri-text > .label-severity[data-severity="3"] {color: #FAAE1A; background-position: 1px -69px;}
+.pri-text > .label-severity[data-severity="4"] {color: #B89664; background-position: 1px -104px;}
.flow-block .footerbar span.label-success {background-color: #0DBB7D;}
.flow-block .footerbar span.label-default {background-color: #9EA3B0;}
diff --git a/www/theme/zui/css/min.css b/www/theme/zui/css/min.css
index cb5b1ab62e..480b229f5c 100644
--- a/www/theme/zui/css/min.css
+++ b/www/theme/zui/css/min.css
@@ -1,5 +1,5 @@
/*!
- * ZUI: ZUI for Zentao - v1.10.0 - 2022-11-21
+ * ZUI: ZUI for Zentao - v1.10.0 - 2022-12-15
* http://openzui.com
* GitHub: https://github.com/easysoft/zui.git
* Copyright (c) 2022 cnezsoft.com; Licensed MIT
@@ -13,4 +13,4 @@
*
* Copyright (c) 2011-2016 Harvest http://getharvest.com
* MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
-*/.chosen-container{position:relative;display:block;font-size:13px;vertical-align:middle;zoom:1;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.chosen-container .chosen-drop{position:absolute;top:100%;z-index:1010;display:none;width:100%;background:#fff;border:1px solid #b2b2b2;border:1px solid rgba(0,0,0,.15);border-top:0;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.chosen-container .chosen-drop.chosen-drop-size-limited{border-top:1px solid rgba(0,0,0,.15)}.chosen-container .chosen-drop.chosen-auto-max-width{min-width:100%;border-top:1px solid rgba(0,0,0,.15);opacity:0}.chosen-container .chosen-drop.chosen-auto-max-width>.chosen-results>li{display:inline-block;white-space:nowrap}.chosen-container .chosen-drop.chosen-auto-max-width.in{opacity:1}.chosen-container .chosen-drop.chosen-auto-max-width.in>.chosen-results>li{display:block;white-space:normal}.chosen-container .chosen-drop.chosen-no-wrap>.chosen-results>li{overflow:hidden;text-overflow:ellipsis;white-space:nowrap!important}.chosen-container.chosen-with-drop .chosen-drop{display:block}.chosen-container a{cursor:pointer}.chosen-container.chosen-up .chosen-drop{top:inherit;bottom:100%;margin-top:auto;margin-bottom:-1px;border-radius:2px 2px 0 0;-webkit-box-shadow:0 -3px 5px rgba(0,0,0,.175);box-shadow:0 -3px 5px rgba(0,0,0,.175)}.chosen-container.chosen-highlight-selected .result-selected{color:#2e7fff;background:#e6f0ff}.chosen-container-single .chosen-single{display:block;width:100%;height:32px;padding:5px 8px;overflow:hidden;line-height:1.42857143;color:#0b0f18;text-decoration:none;white-space:nowrap;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #dcdcdc;border-radius:2px;-webkit-box-shadow:none;box-shadow:none;-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s,-webkit-box-shadow ease-in-out .15s}.chosen-container-single .chosen-default{color:#838a9d}.chosen-container-single .chosen-single>span{display:block;margin-right:26px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chosen-container-single .chosen-single-with-deselect span{margin-right:38px}.chosen-container-single .chosen-single abbr{position:absolute;top:5px;right:24px;display:block;width:20px;height:20px;font-family:sans-serif;font-size:18px;font-weight:700;line-height:18px;color:#000;text-align:center;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.chosen-container-single .chosen-single abbr:before{display:block;content:'×'}.chosen-container-single .chosen-single abbr:focus,.chosen-container-single .chosen-single abbr:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}.chosen-container-single .chosen-single div{position:absolute;top:0;right:0;display:block;height:100%;padding:5px 8px}.chosen-container-single .chosen-single div b{display:inline-block;width:0;height:0;margin-bottom:2px;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent;opacity:.5}.chosen-container-single .chosen-search{position:relative;z-index:1010;padding:3px 4px;margin:0;white-space:nowrap}.chosen-container-single .chosen-search input[type=text]{width:100%;height:27px;padding:2px 26px 2px 8px;margin:1px 0;font-size:12px;line-height:1.5;background-color:#fff;border:1px solid #dcdcdc;border-radius:2px;outline:0}.chosen-container-single .chosen-search input[type=text]:focus{border-color:#2e7fff}.chosen-container-single .chosen-search:before{position:absolute;top:10px;right:10px;display:block;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;color:#838a9d;text-transform:none;content:"\e928";speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.chosen-container-single .chosen-drop{margin-top:-1px;-webkit-background-clip:padding-box;background-clip:padding-box;border-radius:0 0 4px 4px}.chosen-container-single.chosen-container-single-nosearch .chosen-search{position:absolute;left:-9999px}.chosen-container .chosen-results{position:relative;max-height:240px;padding:0;margin:0;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch}.chosen-container .chosen-results li{display:none;padding:5px 10px;margin:0;line-height:15px;list-style:none;-webkit-transition:background-color .2s cubic-bezier(.175,.885,.32,1);-o-transition:background-color .2s cubic-bezier(.175,.885,.32,1);transition:background-color .2s cubic-bezier(.175,.885,.32,1);-webkit-touch-callout:none}.chosen-container .chosen-results li.active-result{display:list-item;cursor:pointer}.chosen-container .chosen-results li.disabled-result{display:list-item;color:#ccc;cursor:default}.chosen-container .chosen-results li.highlighted{color:#fff;background-color:#2e7fff}.chosen-container .chosen-results li.no-results{display:list-item;background:#f4f4f4}.chosen-container .chosen-results li.group-result{display:list-item;font-weight:700;cursor:default}.chosen-container .chosen-results li.group-option{padding-left:15px}.chosen-container .chosen-results li em{font-style:normal;text-decoration:underline}.chosen-container-multi .chosen-choices{position:relative;width:100%;min-height:32px;min-height:30px\9;padding:0;margin:0;overflow:hidden;cursor:text;background-color:#fff;border:1px solid #dcdcdc;border-radius:2px;-webkit-box-shadow:none;box-shadow:none;-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s,-webkit-box-shadow ease-in-out .15s}.chosen-container-multi .chosen-choices:after,.chosen-container-multi .chosen-choices:before{display:table;content:" "}.chosen-container-multi .chosen-choices:after{clear:both}.chosen-container-multi .chosen-choices li{display:block;float:left;padding:0 6px;margin:5px 4px;list-style:none}.chosen-container-multi .chosen-choices li.search-field{padding:0;line-height:12px;white-space:nowrap}.chosen-container-multi .chosen-choices li.search-field input[type=text]{height:20px;font-size:100%;color:#838a9d;background:0 0!important;border:0!important;border-radius:0;outline:0;-webkit-box-shadow:none;box-shadow:none}.chosen-container-multi .chosen-choices li.search-field .default{color:#999}.chosen-container-multi .chosen-choices li.search-field:before{position:absolute;right:8px;bottom:8px;display:block;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;color:#838a9d;text-transform:none;content:"\e928";opacity:0;-webkit-transition:opacity .2s cubic-bezier(.175,.885,.32,1);-o-transition:opacity .2s cubic-bezier(.175,.885,.32,1);transition:opacity .2s cubic-bezier(.175,.885,.32,1);speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.chosen-container-multi .chosen-choices li.search-choice{position:relative;padding:3px 20px 3px 5px;line-height:12px;cursor:default;background-color:#f1f1f1;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #c4c4c4;border-radius:3px;-webkit-box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);-webkit-transition:all .4s cubic-bezier(.175,.885,.32,1);-o-transition:all .4s cubic-bezier(.175,.885,.32,1);transition:all .4s cubic-bezier(.175,.885,.32,1)}.chosen-container-multi .chosen-choices li.search-choice:hover{background-color:#fff;border-color:#ababab;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.1);box-shadow:0 1px 0 rgba(0,0,0,.1)}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close{position:absolute;top:1px;right:0;display:block;width:20px;height:18px;line-height:18px;color:#000;text-align:center;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:before{font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-shadow:0 1px 0 #fff;text-transform:none;content:'\d7';speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:focus,.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}.chosen-container-multi .chosen-choices li.search-choice-disabled{padding-right:5px;color:#666;background-color:#e4e4e4;border:1px solid #ccc}.chosen-container-multi .chosen-choices li.search-choice-focus{background:#d4d4d4}.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close{background-position:-42px -10px}.chosen-container-multi .chosen-results{padding:5px 0;margin:0}.chosen-container-multi .chosen-drop .result-selected{display:list-item;color:#ccc;cursor:default}.chosen-container-active .chosen-single{border-color:#2e7fff;-webkit-box-shadow:none,0 0 8px rgba(46,127,255,.6);box-shadow:none,0 0 8px rgba(46,127,255,.6)}.chosen-container-active.chosen-with-drop .chosen-single{border:1px solid #b2b2b2;border:1px solid rgba(0,0,0,.15);border-bottom-right-radius:0!important;border-bottom-left-radius:0!important;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.chosen-container-active.chosen-with-drop .chosen-single div{background:0 0;border-left:none}.chosen-container-active.chosen-with-drop .chosen-single div b{content:"";border-top:0 dotted;border-bottom:4px solid}.chosen-container-active.chosen-with-drop.chosen-up .chosen-single{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:2px;border-bottom-left-radius:2px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.chosen-container-active .chosen-choices{border-color:#2e7fff;-webkit-box-shadow:none,0 0 8px rgba(46,127,255,.6);box-shadow:none,0 0 8px rgba(46,127,255,.6)}.chosen-container-active .chosen-choices li.search-field input[type=text]{color:#111!important}.chosen-container-active .chosen-choices li.search-field:before{opacity:1}.chosen-disabled{cursor:default;opacity:.5!important}.chosen-disabled .chosen-single{cursor:default}.chosen-disabled .chosen-choices .search-choice .search-choice-close{cursor:default}.chosen-compact.chosen-container-single .chosen-single>.chosen-search{left:0;display:none;padding:3px 4px;opacity:0}.chosen-compact.chosen-container-single .chosen-single>.chosen-search>input{height:25px;padding:2px 26px 2px 4px;font-size:inherit}.chosen-compact.chosen-container-single .chosen-single>.chosen-search:before{top:9px}.chosen-compact.chosen-with-search.chosen-with-drop .chosen-single>.chosen-search{display:block;opacity:1}select.chosen[multiple]{height:32px;overflow:hidden}select.chosen[multiple] option{visibility:hidden}.picker{min-height:32px;background-color:#eee}.picker:not(.picker-ready)>*{display:none}.picker-selections{position:relative;min-height:32px;color:#0b0f18;background-color:#fff;border:1px solid #dcdcdc;border-radius:2px;-webkit-box-shadow:none;box-shadow:none}.picker-focus .picker-selections{border-color:#2e7fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(46,127,255,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(46,127,255,.6)}.disabled .picker-selections{cursor:not-allowed;background-color:#f5f5f5}.picker-multi .picker-selections{padding:3px}.picker-multi.picker-focus .picker-selections:before{position:absolute;right:0;bottom:8px;display:block;width:24px;height:14px;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-align:center;text-transform:none;content:"\e928";opacity:.4;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.picker-multi.picker-focus.picker-loading .picker-selections::before{display:inline-block;content:"\e982";-webkit-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}.picker-multi .picker-selection{position:relative;display:inline-block;padding:0 20px 0 5px;margin:2px;line-height:18px;background-color:#f1f1f1;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #c4c4c4;border-radius:3px;-webkit-box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05)}.picker-selection-single{max-width:100%;padding:5px 20px 5px 8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.picker-focus .picker-selection-single .picker-selection-text{display:none}.picker-selection-single:after{position:absolute;top:7px;right:0;display:block;width:24px;height:14px;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-align:center;text-transform:none;content:"\f0d7";opacity:.4;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.picker-focus .picker-selection-single::after{content:"\e928"}.picker-focus.picker-loading .picker-selection-single::after{display:inline-block;content:"\e982";-webkit-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}.picker-selection-remove{position:absolute;top:0;right:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:18px;height:18px;text-align:center;cursor:pointer;opacity:.2;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.picker-selection-remove:before{font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-shadow:0 1px 0 #fff;text-transform:none;content:'\d7';speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.picker-selection-remove:hover{opacity:.5}.picker-single .picker-selection-remove{top:5px;right:20px;z-index:1}.picker-no-value .picker-selection-remove,.picker-single.picker-focus .picker-selection-remove{display:none}.picker-multi.picker-sortable .picker-selection-text{cursor:move}.picker-search{color:transparent;background:0 0;border:none}.picker-search:focus{color:inherit;outline:0}.disabled .picker-search{cursor:not-allowed}.picker-single .picker-search{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;padding:5px 8px}.picker-multi .picker-search{display:inline-block;width:10px;min-width:10px;max-width:100%;padding:0 2px;margin:2px 0}.picker-placeholder{position:absolute;top:5px;right:20px;left:8px;z-index:0;display:none;overflow:hidden;color:#838a9d;text-overflow:ellipsis;white-space:nowrap}.picker-no-value.picker-focus .picker-placeholder{display:none}.picker-input-empty.picker-focus.picker-no-value .picker-placeholder,.picker-no-value .picker-placeholder{display:block}.picker-drop-menu{position:fixed;z-index:1200;display:none;min-width:100px;background-color:#fff;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.12),0 1px 3px rgba(0,0,0,.1);box-shadow:0 6px 12px rgba(0,0,0,.12),0 1px 3px rgba(0,0,0,.1);opacity:0}.picker-drop-menu.picker-drop-show{display:block}.picker-option-list{padding:5px 0;margin:0;overflow-y:auto}.picker-no-options .picker-option-list{display:none}.picker-option{display:block;min-height:30px;padding:5px 10px;overflow:hidden;clear:both;font-weight:400;line-height:1.42857143;color:#313c52;text-overflow:ellipsis;white-space:nowrap;-webkit-transition:none;-o-transition:none;transition:none}.picker-single .picker-option.picker-option-selected{color:#2e7fff;background:#e6f0ff}.picker-option:focus,.picker-option:hover{text-decoration:none}.picker-option.picker-option-active,.picker-single .picker-option.picker-option-selected.picker-option-active{color:#fff;background-color:#2e7fff}.picker-multi .picker-option.picker-option-selected,.picker-multi .picker-option.picker-option-selected.picker-option-active{color:#ccc;cursor:not-allowed;background-color:#fff}.picker-option.disabled{cursor:not-allowed!important}.picker-option-text-matched{position:relative;color:#2e7fff}.picker-option-text-matched:before{position:absolute;right:0;bottom:-4px;left:0;display:block;height:2px;content:' ';background-color:#2e7fff;border-radius:2px;opacity:.5}.picker-option-active .picker-option-text-matched,.picker-option:hover .picker-option-text-matched{color:#fff}.picker-option-active .picker-option-text-matched:before,.picker-option:hover .picker-option-text-matched:before{background-color:#fff}.picker-message{display:none;height:22px;padding:2px 5px;overflow:hidden;font-size:12px;color:#838a9d;text-overflow:ellipsis;white-space:nowrap;background-color:#f1f1f1}.picker-has-message .picker-message{display:block}.picker-message[data-type=danger]{color:#fc5959}.picker-actions{padding:5px;border-top:1px solid #eee}.picker-no-actions .picker-actions{display:none}.picker-actions .btn{padding:2px 4px}.picker-actions .btn[disabled]{background-color:transparent;filter:grayscale(1);-webkit-filter:grayscale(1)}.picker-actions .btn+.btn{margin-left:4px}.picker-checkable .picker-option{position:relative;padding-left:32px;color:inherit!important;cursor:pointer!important}.picker-checkable .picker-option>.checkbox-primary{position:absolute;top:4px;left:10px}.picker-checkable .picker-option.picker-option-active{background-color:rgba(0,0,0,.05)!important}.picker-checkable .picker-option:hover{background-color:rgba(0,0,0,.075)!important}.picker-checkable .picker-option.picker-option-selected{background-color:#e6f0ff!important}.picker-checkable .picker-option-active .picker-option-text-matched,.picker-checkable .picker-option:hover .picker-option-text-matched{color:#2e7fff!important}.picker-checkable .picker-option-active .picker-option-text-matched:before,.picker-checkable .picker-option:hover .picker-option-text-matched:before{background:#2e7fff!important}:root{--color-primary:#2e7fff;--color-secondary:#18a6fd;--color-pale:#e6f0ff;--color-fore:#313c52;--color-back:#fff;--color-red:#fc5959;--color-yellow:#ff9f46;--color-green:#22c98d;--color-blue:#37b2fe;--color-purple:#8166ee;--color-brown:#ff6f42;--color-danger:#fc5959;--color-warning:#ff9f46;--color-success:#22c98d;--color-info:#37b2fe;--color-special:#8166ee;--color-important:#ff6f42;--color-green-pale:#ecfff8;--color-yellow-pale:#fff4ea;--color-red-pale:#ffebeb;--color-blue-pale:#e7f6ff;--color-brown-pale:#fff2ed;--color-purple-pale:#efecfa;--link-color:#313c52;--link-hover-color:#2e7fff;--color-gray-darker:#0b0f18;--color-gray-dark:#313c52;--color-gray:#838a9d;--color-gray-light:#c4c4c4;--color-gray-lighter:#eee;--color-gray-pale:#f1f1f1;--border-radius:4px;--border-radius-lg:6px;--border-radius-sm:3px;--bg-gradient:#3785ff}.container,.container-fixed,.container-fluid{position:relative}.container{max-width:1800px!important}body{color:#313c52;background-color:#f4f5f7}body.article-content,body.body-modal{background:0 0}body.body-modal{padding:0}@media screen and (min-width:1920px){body{font-size:14px}}a:active,a:focus,button:active,button:focus{outline:0!important}.strong{font-weight:700}.font-normal{font-weight:400!important}.text-middle{vertical-align:middle!important}.text-bottom{vertical-align:bottom!important}.text-top{vertical-align:top!important}.inline-block{display:inline-block!important}.layer{border-radius:4px;-webkit-box-shadow:0 0 20px 0 #bdc9d8;box-shadow:0 0 20px 0 #bdc9d8}.space{margin-bottom:20px}.space-lg{margin-bottom:30px}.space-sm{margin-bottom:10px}.muted{opacity:.5}.text-muted em{color:#313c52}.no-animate{-webkit-transition:none!important;-o-transition:none!important;transition:none!important}.template{display:none!important}.text-left{text-align:left!important}.text-yellow.icon-folder{color:#ff9f46}.table-row{display:table;width:100%;table-layout:fixed}.table-col,.table-row>.col,.table-row>[class*=col-],.table-row>[class*="-col"]{display:table-cell;float:none;vertical-align:top}.side-col{width:200px;padding-right:20px}.side-col.col-4{width:33.3333333%}.col-lg{width:260px}.col-xl{width:320px}.col-sm{width:150px}.col-xs{width:100px}.main-col+.side-col{padding-right:0;padding-left:20px}.row-grid>[class*=col-],.row-grid>[class*="-col"]{padding-top:6px;padding-bottom:6px}hr.space{margin:10px 0;border:none}hr.space-sm{margin:5px 0;border:none}.text-secondary{color:#18a6fd}a.text-primary{color:#2e7fff}.text-primary-400{color:#3785ff}.text-primary-600{color:#1e6aeb}.text-primary-700{color:#2463c7}.text-primary-900{color:#063072}.text-green-400{color:#48d1a0}.text-yellow-400{color:#ffaf65}.text-blue-400{color:#60c2ff}.text-red-400{color:#ff7c7c}.text-important-400{color:#ff9777}.text-purple-400{color:#8a6ff5}.text-green-600{color:#0dbb7d}.text-yellow-600{color:#f38f19}.text-blue-600{color:#00c0e9}.text-red-600{color:#fb2b2b}.text-important-600{color:#ff6f42}.text-purple-600{color:#7055df}.text-green-700{color:#0ca76f}.text-yellow-700{color:#ed8307}.text-blue-700{color:#1099ed}.text-red-700{color:#d91b1b}.text-important-700{color:#ff4912}.text-purple-700{color:#513baa}.text-green-900{color:#186c4e}.text-yellow-900{color:#af5f01}.text-blue-900{color:#046bab}.text-red-900{color:#a20606}.text-important-900{color:#611700}.text-purple-900{color:#30216f}.text-gray-14,.text-title{color:#0b0f18}.icon-important,.text-color,.text-gray-13{color:#313c52}.text-gray-12{color:#5e626d}.icon-common,.text-gray-11{color:#838a9d}.icon-secondary,.text-gray-10{color:#9ea3b0}.text-disabled{color:#c4c4c4}.nav-primary>li>a{min-width:100px;padding:5px 8px;color:#838a9d;border-color:#e7f1fc}.nav-primary>li.active>a{color:#2e7fff;background-color:#e7f1fc;border-color:#e7f1fc}.nav-primary>li.active>a:hover{color:#2e7fff;background-color:#c3dcf7;border-color:#c3dcf7}.end-marker{margin-bottom:20px;color:#c4c4c4;text-align:center}@-webkit-keyframes highlight{0%{background:#fff;outline:1px solid transparent}100%{background:#fff0d5;outline:2px solid #ffdcbc}}@-o-keyframes highlight{0%{background:#fff;outline:1px solid transparent}100%{background:#fff0d5;outline:2px solid #ffdcbc}}@keyframes highlight{0%{background:#fff;outline:1px solid transparent}100%{background:#fff0d5;outline:2px solid #ffdcbc}}.highlight{-webkit-animation:highlight .5s linear 0s 2 alternate;-o-animation:highlight .5s linear 0s 2 alternate;animation:highlight .5s linear 0s 2 alternate}.progress.inline-block{width:100px;margin:0}.w-p5{width:5%!important}.w-p10{width:10%!important}.w-p15{width:15%!important}.w-p20{width:20%!important}.w-p25{width:25%!important}.w-p30{width:30%!important}.w-p35{width:35%!important}.w-p40{width:40%!important}.w-p45{width:45%!important}.w-p50{width:50%!important}.w-p55{width:55%!important}.w-p60{width:60%!important}.w-p65{width:65%!important}.w-p70{width:70%!important}.w-p75{width:75%!important}.w-p80{width:80%!important}.w-p85{width:85%!important}.w-p90{width:90%!important}.w-p94{width:94%!important}.w-p95{width:95%!important}.w-p98{width:98%!important}.w-p99{width:99%!important}.w-p100{width:100%!important}.w-auto{width:auto!important}.w-10px{width:10px!important}.w-20px{width:20px!important}.w-30px{width:30px!important}.w-35px{width:35px!important}.w-40px{width:40px!important}.w-45px{width:45px!important}.w-50px{width:50px!important}.w-60px{width:60px!important}.w-70px{width:70px!important}.w-80px{width:80px!important}.w-90px{width:90px!important}.w-100px{width:100px!important}.w-110px{width:110px!important}.w-120px{width:120px!important}.w-130px{width:130px!important}.w-140px{width:140px!important}.w-150px{width:150px!important}.w-160px{width:160px!important}.w-180px{width:180px!important}.w-200px{width:200px!important}.w-230px{width:230px!important}.w-250px{width:250px!important}.w-300px{width:300px!important}.w-400px{width:400px!important}.w-500px{width:500px!important}.w-600px{width:600px!important}.w-700px{width:700px!important}.w-800px{width:800px!important}.w-900px{width:900px!important}.mw-200px{max-width:200px!important}.mw-300px{max-width:300px!important}.mw-400px{max-width:400px!important}.mw-500px{max-width:500px!important}.mw-600px{max-width:600px!important}.mw-700px{max-width:700px!important}.mw-800px{max-width:800px!important}.mw-900px{max-width:900px!important}.mw-1400px{max-width:1400px!important}.w-id{width:70px!important}.w-pri{width:40px!important}.w-severity{width:50px!important}.w-hour{width:57px!important}.w-date{width:90px!important}.w-status{width:60px!important}.w-resolution,.w-type,.w-user{width:80px!important}.w-p15-f{width:15%!important;min-width:120px!important}.w-p25-f{width:25%!important;min-width:200px!important}.w-p35-f{width:35%!important;min-width:300px!important}.w-p45-f{width:45%!important;min-width:400px!important}.h-5px{height:5px!important}.h-10px{height:10px!important}.h-20px{height:20px!important}.h-30px{height:30px!important}.h-35px{height:35px!important}.h-40px{height:40px!important}.h-45px{height:45px!important}.h-50px{height:50px!important}.h-60px{height:60px!important}.h-70px{height:70px!important}.h-80px{height:80px!important}.h-100px{height:100px!important}.h-120px{height:120px!important}.h-130px{height:130px!important}.h-140px{height:140px!important}.h-150px{height:150px!important}.h-200px{height:200px!important}.pd-0{padding:0!important}.mg-0{margin:0!important}.mgb-20{margin-bottom:20px!important}.mgb-10{margin-bottom:10px!important}.pdb-20{padding-bottom:20px!important}.pdt-20{padding-top:20px!important}.br-0{border-radius:0!important}.bd-0,.bd-none,.borderless{border:none!important}.bg-none{background:0 0!important}.red{color:#fc5959!important}.icon-pro-version{font-size:14px!important}.icon-pro-version:before{position:relative;top:-1px;font-size:14px;color:#fc5959;content:"\e92b"}.bg-primary{color:#fff;background:#3785ff;border-top-color:#2e7fff}.bg-primary-pure{color:#fff;background-color:#2e7fff}.bg-primary-pale{color:#2e7fff;background-color:#e6f0ff}.bg-secondary{color:#fff;background:#18a6fd}.line-gray-8{background:#d8dbde}.line-gray-5{background:#e3e4e9}.line-gray-7{background:#eee}.bg-gray-1{background:#e3e4e9}.bg-gray-2{background:#edeef2}.bg-gray-3{background:#f8f8f8}.bg-green-400{background:#48d1a0}.bg-yellow-400{background:#ffaf65}.bg-blue-400{background:#60c2ff}.bg-red-400{background:#ff7c7c}.bg-important-400{background:#ff9777}.bg-purple-400{background:#8a6ff5}.bg-green-600{background:#0dbb7d}.bg-yellow-600{background:#f38f19}.bg-blue-600{background:#00c0e9}.bg-red-600{background:#fb2b2b}.bg-important-600{background:#ff6f42}.bg-purple-600{background:#7055df}.bg-green-700{background:#0ca76f}.bg-yellow-700{background:#ed8307}.bg-blue-700{background:#1099ed}.bg-red-700{background:#d91b1b}.bg-important-700{background:#ff4912}.bg-purple-700{background:#513baa}.bg-green-900{background:#186c4e}.bg-yellow-900{background:#af5f01}.bg-blue-900{background:#046bab}.bg-red-900{background:#a20606}.bg-important-900{background:#611700}.bg-purple-900{background:#30216f}.bg-hover{background:#f6f6f6}.bg-active{background:#e6eaf1}.hl-tutorial{position:relative!important;z-index:1010!important;-webkit-box-shadow:0 0 0 0 #000!important;box-shadow:0 0 0 0 #000!important;-webkit-transition:-webkit-box-shadow 1s!important;-o-transition:box-shadow 1s!important;transition:-webkit-box-shadow 1s!important;transition:box-shadow 1s!important;transition:box-shadow 1s,-webkit-box-shadow 1s!important}.hl-tutorial.hl-in{-webkit-box-shadow:0 0 20px 0 #ffff8d,0 0 0 2px #ffd180,0 0 0 3000px rgba(0,0,0,.2)!important;box-shadow:0 0 20px 0 #ffff8d,0 0 0 2px #ffd180,0 0 0 3000px rgba(0,0,0,.2)!important}.btn.tooltip-tutorial,.hl-tutorial.hl-in:hover{position:relative!important;z-index:1010!important;-webkit-box-shadow:0 0 30px 0 #ffff8d,0 0 0 5px #ffd180,0 0 0 3000px rgba(0,0,0,.3)!important;box-shadow:0 0 30px 0 #ffff8d,0 0 0 5px #ffd180,0 0 0 3000px rgba(0,0,0,.3)!important}.tooltip-max .tooltip-inner{max-width:1000px;padding:8px 10px}.transition-all *{-webkit-transition:all .2s!important;-o-transition:all .2s!important;transition:all .2s!important}.scroll-x{overflow-x:auto!important}.scroll-y{overflow-y:auto!important}.divider+.divider{display:none}.ie *{-webkit-transition:none!important;-o-transition:none!important;transition:none!important}@font-face{font-family:Oswald;font-weight:400;src:url(../fonts/Oswald-Regular.ttf)}@font-face{font-family:Oswald;font-weight:500;src:url(../fonts/Oswald-Medium.ttf)}@font-face{font-family:Oswald;font-weight:300;src:url(../fonts/Oswald-Light.ttf)}.num{font-family:Oswald;font-weight:400}@font-face{font-family:ZentaoIcon;font-style:normal;font-weight:400;src:url(../fonts/ZentaoIcon.eot?v=1.34);src:url(../fonts/ZentaoIcon.eot?#iefix&v=1.34) format('embedded-opentype'),url(../fonts/ZentaoIcon.woff?v=1.34) format('woff'),url(../fonts/ZentaoIcon.ttf?v=1.34) format('truetype'),url(../fonts/ZentaoIcon.svg#regular?v=1.34) format('svg')}.icon,[class*=" icon-"],[class^=icon-]{font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-transform:none;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon:before{display:inline-block;min-width:14px;text-align:center}a .icon,a [class*=" icon-"],a [class^=icon-]{display:inline}.icon-lg:before{font-size:1.33333333em;vertical-align:-10%}.icon-2x{font-size:28px}.icon-3x{font-size:42px}.icon-4x{font-size:56px}.icon-5x{font-size:70px}.icon-spin{display:inline-block;-webkit-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}a .icon-spin{display:inline-block;text-decoration:none}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0);transform:rotate(0)}100%{-o-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);-o-transform:rotate(359deg);transform:rotate(359deg)}}.icon-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.icon-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.icon-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.icon-flip-horizontal{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);-o-transform:scale(-1,1);transform:scale(-1,1)}.icon-flip-vertical{-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);-o-transform:scale(1,-1);transform:scale(1,-1)}.icon.icon-flip-horizontal,.icon.icon-flip-vertical,.icon.icon-rotate-180,.icon.icon-rotate-270,.icon.icon-rotate-90{display:inline-block}.icon-zentao:before{content:"\e901"}.icon-zentao-alt:before{content:"\e900"}.icon-help:before{content:"\e968"}.icon-import:before{content:"\e904"}.icon-download:before{content:"\e904"}.icon-export:before{content:"\e905"}.icon-lightbulb:before{content:"\e91c"}.icon-close:before{content:"\e936"}.icon-check:before{content:"\e5ca"}.icon-plus:before{content:"\e925"}.icon-minus:before{content:"\e926"}.icon-expand-alt:before{content:"\e6f1"}.icon-collapse-alt:before{content:"\e6f2"}.icon-fullscreen:before{content:"\e96b"}.icon-star-empty:before{content:"\e94a"}.icon-star:before{content:"\e94b"}.icon-exclamation-sign:before{content:"\e930"}.icon-info-sign:before{content:"\e9d5"}.icon-flag:before{content:"\e937"}.icon-check-circle:before{content:"\e92f"}.icon-check-sign:before{content:"\e938"}.icon-chart-pie:before{content:"\e95b"}.icon-history:before{content:"\e95f"}.icon-pencil:before{content:"\e254"}.icon-search:before{content:"\e928"}.icon-restart:before{content:"\e95e"}.icon-cog:before{content:"\e93b"}.icon-chart-line:before{content:"\e95c"}.icon-chart-bar:before{content:"\e95d"}.icon-bar-chart:before{content:"\e95d"}.icon-exchange:before{content:"\e927"}.icon-severity:before{content:"\e973"}.icon-book:before{content:"\f02d"}.icon-treemap-alt:before{content:"\e971"}.icon-severity-solid:before{content:"\e902"}.icon-chat-line:before{content:"\e998"}.icon-stack:before{content:"\e943"}.icon-cube:before{content:"\e967"}.icon-minus-sign:before{content:"\e939"}.icon-bars-sign:before{content:"\e93a"}.icon-chat:before{content:"\e940"}.icon-message:before{content:"\e940"}.icon-more:before{content:"\e744"}.icon-certificate:before{content:"\f0a3"}.icon-bell:before{content:"\e7f5"}.icon-columns:before{content:"\f0db"}.icon-envelope-o:before{content:"\e92a"}.icon-unfold-all:before{content:"\e931"}.icon-fold-all:before{content:"\e932"}.icon-bars:before{content:"\e948"}.icon-cards-view:before{content:"\e949"}.icon-ellipsis-v:before{content:"\e5d4"}.icon-spinner-indicator:before{content:"\e982"}.icon-up-circle:before{content:"\e92b"}.icon-right-circle:before{content:"\e92c"}.icon-down-circle:before{content:"\e92d"}.icon-left-circle:before{content:"\e92e"}.icon-angle-double-right:before{content:"\f101"}.icon-angle-down:before{content:"\e313"}.icon-angle-left:before{content:"\e314"}.icon-angle-right:before{content:"\e315"}.icon-angle-top:before{content:"\e316"}.icon-first-page:before{content:"\e5dc"}.icon-last-page:before{content:"\e5dd"}.icon-caret-down:before{content:"\f0d7"}.icon-caret-up:before{content:"\f0d8"}.icon-caret-left:before{content:"\f0d9"}.icon-caret-right:before{content:"\f0da"}.icon-sort:before{content:"\f0dc"}.icon-sort-down:before{content:"\f0dd"}.icon-sort-up:before{content:"\f0de"}.icon-arrow-up:before{content:"\e923"}.icon-arrow-down:before{content:"\e924"}.icon-arrow-left:before{content:"\e952"}.icon-arrow-right:before{content:"\e93e"}.icon-chevron-left:before{content:"\e934"}.icon-chevron-right:before{content:"\e935"}.icon-chevron-double-up:before{content:"\e959"}.icon-chevron-double-down:before{content:"\e95a"}.icon-folder-account:before{content:"\e942"}.icon-folder-move:before{content:"\e960"}.icon-folder-plus:before{content:"\e961"}.icon-folder-upload:before{content:"\e962"}.icon-folder-star:before{content:"\e963"}.icon-folder-edit:before{content:"\e964"}.icon-folder-download:before{content:"\e965"}.icon-folder-outline:before{content:"\e966"}.icon-folder:before{content:"\e944"}.icon-folder-o:before{content:"\e945"}.icon-folder-open-o:before{content:"\e946"}.icon-folder-open:before{content:"\e947"}.icon-color:before{content:"\e93c"}.icon-paper-clip:before{content:"\e93d"}.icon-text:before{content:"\e929"}.icon-share:before{content:"\f064"}.icon-format-list-bulleted:before{content:"\e9a8"}.icon-format-bold:before{content:"\e953"}.icon-format-header-pound:before{content:"\e954"}.icon-format-italic:before{content:"\e955"}.icon-format-list-numbers:before{content:"\e969"}.icon-format-quote-close:before{content:"\e96a"}.icon-image:before{content:"\e96c"}.icon-table-large:before{content:"\e96d"}.icon-aiux:before{content:"\e99e"}.icon-qc:before{content:"\e986"}.icon-qc-q:before{content:"\e985"}.icon-qc-c:before{content:"\e987"}.icon-sonarqube:before{content:"\e9ba"}.icon-college:before{content:"\e9c8"}.icon-ztool:before{content:"\e9c1"}.icon-contacts:before{content:"\e9c3"}.icon-chats:before{content:"\e9c4"}.icon-menu-my:before{content:"\e97a"}.icon-home:before{content:"\e97a"}.icon-program:before{content:"\e9aa"}.icon-lightbulb-alt:before{content:"\e98f"}.icon-product:before{content:"\e98f"}.icon-rocket:before{content:"\e99c"}.icon-project:before{content:"\e99c"}.icon-run:before{content:"\e9a9"}.icon-test:before{content:"\e956"}.icon-infinite:before{content:"\e9a3"}.icon-devops:before{content:"\e9a3"}.icon-ops:before{content:"\e903"}.icon-doc:before{content:"\e99b"}.icon-menu-doc:before{content:"\e99b"}.icon-statistic:before{content:"\e999"}.icon-menu-backend:before{content:"\e993"}.icon-assets:before{content:"\e9ae"}.icon-diamond:before{content:"\e9ae"}.icon-feedback:before{content:"\e991"}.icon-flow:before{content:"\e994"}.icon-oa:before{content:"\e9a1"}.icon-more-circle:before{content:"\e988"}.icon-controls:before{content:"\e995"}.icon-account:before{content:"\e992"}.icon-about:before{content:"\e996"}.icon-info:before{content:"\e996"}.icon-cog-outline:before{content:"\e997"}.icon-backend:before{content:"\e997"}.icon-exit:before{content:"\e99a"}.icon-theme:before{content:"\e9a0"}.icon-globe:before{content:"\f0ac"}.icon-lang:before{content:"\f0ac"}.icon-table-sort:before{content:"\e9e1"}.icon-blame:before{content:"\e9e0"}.icon-draft-edit:before{content:"\e9dd"}.icon-sub-review:before{content:"\e9df"}.icon-sub-review-user:before{content:"\e9db"}.icon-ztf:before{content:"\e9dc"}.icon-save:before{content:"\e9d8"}.icon-list-box:before{content:"\e9b4"}.icon-usecase:before{content:"\e99d"}.icon-code:before{content:"\e990"}.icon-summary:before{content:"\e9ad"}.icon-more-alt:before{content:"\e9a7"}.icon-customer:before{content:"\e9d9"}.icon-ticket:before{content:"\e9e1"}.icon-gantt-alt:before{content:"\e9e2"}.icon-appose:before{content:"\e9e2"}.icon-inline:before{content:"\e9e3"}.icon-tree:before{content:"\e9c9"}.icon-list:before{content:"\e9cb"}.icon-gantt:before{content:"\e9cc"}.icon-group-view:before{content:"\e9cd"}.icon-inherit-space:before{content:"\e9c2"}.icon-card-archive:before{content:"\e9b8"}.icon-col-archive:before{content:"\e9b9"}.icon-col-add-right:before{content:"\e9bb"}.icon-col-add-left:before{content:"\e9bc"}.icon-col-split:before{content:"\e9bd"}.icon-waterfall:before{content:"\e9a4"}.icon-manual:before{content:"\e98d"}.icon-kanban:before{content:"\e983"}.icon-lane:before{content:"\e9b1"}.icon-back:before{content:"\e9d3"}.icon-back-circle:before{content:"\e9da"}.icon-shield:before{content:"\e9ca"}.icon-meh:before{content:"\e9ce"}.icon-frown:before{content:"\e9cf"}.icon-smile:before{content:"\e9d0"}.icon-unlock-solid:before{content:"\e9d1"}.icon-lock-solid:before{content:"\e9d2"}.icon-ver:before{content:"\e9c6"}.icon-publish:before{content:"\e9c7"}.icon-send:before{content:"\e9c7"}.icon-tag:before{content:"\e9be"}.icon-tag-lock:before{content:"\e9bf"}.icon-code-fork:before{content:"\f126"}.icon-branch-lock:before{content:"\e9c0"}.icon-groups:before{content:"\e9af"}.icon-thumbs-up:before{content:"\f087"}.icon-thumbs-down:before{content:"\f088"}.icon-thumbs-up-solid:before{content:"\e9d6"}.icon-thumbs-down-solid:before{content:"\e9d7"}.icon-hash:before{content:"\e9ab"}.icon-version:before{content:"\e9ab"}.icon-p-square:before{content:"\e97b"}.icon-video-play:before{content:"\e97f"}.icon-plus-solid-circle:before{content:"\e974"}.icon-minuse-solid-circle:before{content:"\e9b6"}.icon-s:before{content:"\e975"}.icon-c:before{content:"\e976"}.icon-t:before{content:"\e977"}.icon-guide:before{content:"\e978"}.icon-todo:before{content:"\e979"}.icon-side-left:before{content:"\e9b3"}.icon-side-right:before{content:"\e9b2"}.icon-fullscreen-exit:before{content:"\e972"}.icon-alert:before{content:"\e99f"}.icon-undo:before{content:"\e93f"}.icon-redo:before{content:"\e9d4"}.icon-swap:before{content:"\e9b0"}.icon-chat-solid:before{content:"\e9b5"}.icon-clock:before{content:"\e97c"}.icon-cost:before{content:"\e97d"}.icon-pencil-alt:before{content:"\e984"}.icon-size-height:before{content:"\e9c5"}.icon-file-log:before{content:"\e9de"}.icon-rich-text:before{content:"\e913"}.icon-markdown:before{content:"\e916"}.icon-excel:before{content:"\e933"}.icon-text-link:before{content:"\e94d"}.icon-ppt:before{content:"\e957"}.icon-word:before{content:"\e958"}.icon-doc-lib:before{content:"\e96f"}.icon-file:before{content:"\f016"}.icon-file-empty:before{content:"\f016"}.icon-file-text:before{content:"\f0f6"}.icon-file-alt:before{content:"\f15b"}.icon-file-text-alt:before{content:"\f15c"}.icon-file-pdf:before{content:"\f1c1"}.icon-file-word:before{content:"\f1c2"}.icon-file-excel:before{content:"\f1c3"}.icon-file-powerpoint:before{content:"\f1c4"}.icon-file-image:before{content:"\f1c5"}.icon-file-archive:before{content:"\f1c6"}.icon-file-audio:before{content:"\f1c7"}.icon-file-video:before{content:"\f1c8"}.icon-file-code:before{content:"\f1c9"}.icon-menu-collapse:before{content:"\e980"}.icon-menu-expand:before{content:"\e981"}.icon-group:before{content:"\e97e"}.icon-menu-users:before{content:"\e97e"}.icon-persons:before{content:"\e97e"}.icon-team:before{content:"\e97e"}.icon-estimate:before{content:"\e9ac"}.icon-sprint:before{content:"\e9a2"}.icon-shield-check:before{content:"\e9a5"}.icon-ok:before{content:"\e9a6"}.icon-printer:before{content:"\e906"}.icon-bullhorn:before{content:"\e910"}.icon-person:before{content:"\e941"}.icon-fields:before{content:"\e989"}.icon-trigger:before{content:"\e98a"}.icon-layout:before{content:"\e98b"}.icon-audit:before{content:"\e98c"}.icon-cancel:before{content:"\e951"}.icon-ban-circle:before{content:"\e951"}.icon-eye:before{content:"\e94e"}.icon-eye-off:before{content:"\e96e"}.icon-unlock:before{content:"\e94f"}.icon-lock:before{content:"\e950"}.icon-private:before{content:"\e950"}.icon-move:before{content:"\e94c"}.icon-hand-right:before{content:"\e907"}.icon-checked:before{content:"\e908"}.icon-off:before{content:"\e909"}.icon-start:before{content:"\e90a"}.icon-play:before{content:"\e90a"}.icon-time:before{content:"\e90b"}.icon-edit:before{content:"\e90c"}.icon-trash:before{content:"\e90d"}.icon-link:before{content:"\e90e"}.icon-unlink:before{content:"\e90f"}.icon-bug:before{content:"\e911"}.icon-list-alt:before{content:"\e912"}.icon-change:before{content:"\e970"}.icon-alter:before{content:"\e970"}.icon-glasses:before{content:"\e914"}.icon-review:before{content:"\e914"}.icon-sitemap:before{content:"\e915"}.icon-testcase:before{content:"\e915"}.icon-pluses:before{content:"\e917"}.icon-report-list:before{content:"\e918"}.icon-magic:before{content:"\e919"}.icon-active:before{content:"\e919"}.icon-treemap:before{content:"\e91a"}.icon-confirm:before{content:"\e91b"}.icon-split:before{content:"\e98e"}.icon-delay:before{content:"\e91d"}.icon-calendar:before{content:"\e91d"}.icon-pause:before{content:"\e91e"}.icon-ban:before{content:"\e91f"}.icon-plus-bold:before{content:"\e920"}.icon-copy:before{content:"\e921"}.icon-refresh:before{content:"\e922"}.icon-diff:before{content:"\e9b7"}.icon-sm:before{font-size:14px;vertical-align:10%}.icon-qc{position:relative}.icon-qc:before{width:1em;color:#7cb938;content:"\e985"}.icon-qc:after{position:absolute;top:0;left:0;width:1em;height:1em;font-family:ZentaoIcon;font-size:14px;font-size:inherit;font-style:normal;font-weight:400;font-variant:normal;line-height:1;color:#36a742;text-transform:none;content:"\e987";speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-message.has-dot{position:relative}.icon-message.has-dot::after{position:absolute;top:-3px;right:-5px;display:block;width:6px;height:6px;content:' ';background-color:#fc5959;border-radius:50%}.icon-project{-webkit-transform:scale(1.2);-ms-transform:scale(1.2);-o-transform:scale(1.2);transform:scale(1.2)}.icon-product{-webkit-transform:scale(1.15);-ms-transform:scale(1.15);-o-transform:scale(1.15);transform:scale(1.15)}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:13px;font-weight:400;line-height:18px;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;border-radius:4px;-webkit-transition:.4s cubic-bezier(.175,.885,.32,1);-o-transition:.4s cubic-bezier(.175,.885,.32,1);transition:.4s cubic-bezier(.175,.885,.32,1);-webkit-transition-property:background,border,outline,opacity,-webkit-box-shadow;-o-transition-property:background,border,box-shadow,outline,opacity;transition-property:background,border,outline,opacity,-webkit-box-shadow;transition-property:background,border,box-shadow,outline,opacity;transition-property:background,border,box-shadow,outline,opacity,-webkit-box-shadow}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:focus,.btn:hover{color:#313c52;text-decoration:none}.btn:active{text-decoration:none;background-image:none;outline:0;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.1)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:grayscale(1);-webkit-box-shadow:none;box-shadow:none;opacity:.5;-webkit-filter:grayscale(1)}.btn{color:#313c52;background-color:#fff;border-color:#d8dbde}.btn.active,.btn.hover,.btn:active,.btn:focus,.btn:hover,.open .dropdown-toggle.btn{color:#313c52;background-color:rgba(255,255,255,.8);border-color:#bcc2c7}.btn.active,.btn:active,.open .dropdown-toggle.btn{background-color:#f2f2f2;background-image:none;border-color:#bcc2c7}.btn.disabled,.btn.disabled.active,.btn.disabled:active,.btn.disabled:focus,.btn.disabled:hover,.btn[disabled],.btn[disabled].active,.btn[disabled]:active,.btn[disabled]:focus,.btn[disabled]:hover,fieldset[disabled] .btn,fieldset[disabled] .btn.active,fieldset[disabled] .btn:active,fieldset[disabled] .btn:focus,fieldset[disabled] .btn:hover{color:rgba(49,60,82,.3);background-color:#fff;border-color:#d8dbde}.btn-gray{color:#82899f;background-color:#f1f1f1;border-color:#f1f1f1}.btn-gray.active,.btn-gray.hover,.btn-gray:active,.btn-gray:focus,.btn-gray:hover,.open .dropdown-toggle.btn-gray{color:#82899f;background-color:rgba(241,241,241,.8);border-color:#d8d8d8}.btn-gray.active,.btn-gray:active,.open .dropdown-toggle.btn-gray{background-color:#e4e4e4;background-image:none;border-color:#d8d8d8}.btn-gray.disabled,.btn-gray.disabled.active,.btn-gray.disabled:active,.btn-gray.disabled:focus,.btn-gray.disabled:hover,.btn-gray[disabled],.btn-gray[disabled].active,.btn-gray[disabled]:active,.btn-gray[disabled]:focus,.btn-gray[disabled]:hover,fieldset[disabled] .btn-gray,fieldset[disabled] .btn-gray.active,fieldset[disabled] .btn-gray:active,fieldset[disabled] .btn-gray:focus,fieldset[disabled] .btn-gray:hover{color:rgba(130,137,159,.3);background-color:#f1f1f1;border-color:#f1f1f1}.btn-primary{color:#fff;background-color:#2e7fff;border-color:transparent}.btn-primary.active,.btn-primary.hover,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open .dropdown-toggle.btn-primary{color:#fff;background-color:rgba(46,127,255,.8);border-color:rgba(0,0,0,0)}.btn-primary.active,.btn-primary:active,.open .dropdown-toggle.btn-primary{background-color:#156fff;background-image:none;border-color:rgba(0,0,0,0)}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{color:rgba(255,255,255,.3);background-color:#2e7fff;border-color:transparent}.btn-secondary{color:#fff;background-color:#18a6fd;border-color:transparent}.btn-secondary.active,.btn-secondary.hover,.btn-secondary:active,.btn-secondary:focus,.btn-secondary:hover,.open .dropdown-toggle.btn-secondary{color:#fff;background-color:rgba(24,166,253,.8);border-color:rgba(0,0,0,0)}.btn-secondary.active,.btn-secondary:active,.open .dropdown-toggle.btn-secondary{background-color:#029bf9;background-image:none;border-color:rgba(0,0,0,0)}.btn-secondary.disabled,.btn-secondary.disabled.active,.btn-secondary.disabled:active,.btn-secondary.disabled:focus,.btn-secondary.disabled:hover,.btn-secondary[disabled],.btn-secondary[disabled].active,.btn-secondary[disabled]:active,.btn-secondary[disabled]:focus,.btn-secondary[disabled]:hover,fieldset[disabled] .btn-secondary,fieldset[disabled] .btn-secondary.active,fieldset[disabled] .btn-secondary:active,fieldset[disabled] .btn-secondary:focus,fieldset[disabled] .btn-secondary:hover{color:rgba(255,255,255,.3);background-color:#18a6fd;border-color:transparent}.btn-warning{color:#fff;background-color:#ff9f46;border-color:transparent}.btn-warning.active,.btn-warning.hover,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open .dropdown-toggle.btn-warning{color:#fff;background-color:rgba(255,159,70,.8);border-color:rgba(0,0,0,0)}.btn-warning.active,.btn-warning:active,.open .dropdown-toggle.btn-warning{background-color:#ff922c;background-image:none;border-color:rgba(0,0,0,0)}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{color:rgba(255,255,255,.3);background-color:#ff9f46;border-color:transparent}.btn-danger{color:#fff;background-color:#fc5959;border-color:transparent}.btn-danger.active,.btn-danger.hover,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open .dropdown-toggle.btn-danger{color:#fff;background-color:rgba(252,89,89,.8);border-color:rgba(0,0,0,0)}.btn-danger.active,.btn-danger:active,.open .dropdown-toggle.btn-danger{background-color:#fc4040;background-image:none;border-color:rgba(0,0,0,0)}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{color:rgba(255,255,255,.3);background-color:#fc5959;border-color:transparent}.btn-success{color:#fff;background-color:#22c98d;border-color:transparent}.btn-success.active,.btn-success.hover,.btn-success:active,.btn-success:focus,.btn-success:hover,.open .dropdown-toggle.btn-success{color:#fff;background-color:rgba(34,201,141,.8);border-color:rgba(0,0,0,0)}.btn-success.active,.btn-success:active,.open .dropdown-toggle.btn-success{background-color:#1eb37e;background-image:none;border-color:rgba(0,0,0,0)}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{color:rgba(255,255,255,.3);background-color:#22c98d;border-color:transparent}.btn-info{color:#2e7fff;background-color:#e6f0ff;border-color:transparent}.btn-info.active,.btn-info.hover,.btn-info:active,.btn-info:focus,.btn-info:hover,.open .dropdown-toggle.btn-info{color:#2e7fff;background-color:rgba(230,240,255,.8);border-color:rgba(0,0,0,0)}.btn-info.active,.btn-info:active,.open .dropdown-toggle.btn-info{background-color:#cce1ff;background-image:none;border-color:rgba(0,0,0,0)}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{color:rgba(46,127,255,.3);background-color:#e6f0ff;border-color:transparent}.btn-link{padding-right:6px;padding-left:6px;font-weight:400;color:#313c52;text-shadow:none;cursor:pointer;background:0 0;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover,.btn-link[disabled],fieldset[disabled] .btn-link{border-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link:focus,.btn-link:hover{color:#0b0f18;background:#f1f1f1;background:rgba(0,0,0,.075)}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#c4c4c4;text-decoration:none}.btn-lg{padding:11px 16px;font-size:14px;line-height:18px;border-radius:4px}.btn-mini,.btn-sm{padding:3px 8px;font-size:12px;line-height:18px;border-radius:4px}.btn-mini,.btn-xs{padding:0 5px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.btn-wide{min-width:120px}.btn-limit{max-width:180px;padding-right:20px;overflow:hidden;text-overflow:unset;white-space:nowrap}.btn-limit>.caret{position:absolute;top:14px;right:8px}.btn-circle{border-radius:17px}.btn>.label-icon{top:3px;padding:3px;margin:-2px;background-color:rgba(0,0,0,.2);border-radius:12px}.btn>.label-icon>.icon{font-size:16px;line-height:18px}.btn>.icon+.text{margin-left:5px}.btn.btn-sm.btn-circle{border-radius:12px}.btn.btn-sm>.label-icon{top:2px;width:20px;height:20px;padding:1px;line-height:20px}.btn.btn-sm>.label-icon>.icon{position:relative;top:-1px;display:inline-block;font-size:14px;line-height:18px}.btn-icon-left{position:relative;padding-left:35px;overflow:hidden;text-align:right}.btn-icon-left>.label-icon{position:absolute;left:5px;margin:0}.btn-icon-left>.icon{position:absolute;top:0;bottom:0;left:0;display:block;width:30px;line-height:30px;color:#18a6fd;text-align:center;background:#e6f0ff}.btn-icon-left.btn-sm{padding-left:28px}.btn-icon-left.btn-sm>.label-icon{left:2px}.btn-icon-left.btn-sm>.icon{width:24px;line-height:24px}.btn-icon-right{position:relative;padding-right:35px;text-align:left}.btn-icon-right>.label-icon{position:absolute;right:5px;margin:0}.btn-icon-right.btn-sm{padding-right:28px}.btn-icon-right.btn-sm>.label-icon{right:2px}.btn-icon{min-width:32px;padding-right:0;padding-left:0}.btn-icon.btn-sm{width:24px;min-width:24px;height:24px}.btn-group{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group>.btn-group{float:left}.btn-group>.btn{border-radius:0}.btn-group>.btn:first-child{border-top-left-radius:2px;border-bottom-left-radius:2px}.btn-group>.btn:last-child{border-top-right-radius:2px;border-bottom-right-radius:2px}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.dropdown{float:left;margin-right:10px}.btn-toolbar>.btn-group:last-child,.btn-toolbar>.btn:last-child,.btn-toolbar>.dropdown:last-child{margin-right:0}.btn-toolbar>.divider{float:left;height:20px;margin:7px 5px 4px 10px;border-left:1px solid rgba(0,0,0,.1)}.btn-toolbar .space{float:left;min-height:1px;margin:0 10px 0 10px}.btn-toolbar .input-control{float:left;width:120px}.btn-toolbar .page-title{float:left;line-height:34px}.btn-toolbar .page-title .text{font-size:14px;font-weight:700}.btn-toolbar .page-title .label{top:-2px;margin-right:10px}.btn-toolbar .divider+.page-title{margin-left:15px}.btn-active-line{position:relative;font-weight:700;color:#2e7fff}.btn-active-line:after{position:absolute;right:5px;bottom:1px;left:5px;display:block;content:' ';border-bottom:2px solid #2e7fff}.btn-active-line:hover{color:#2e7fff}.btn-active-text .text{position:relative;top:-1px;display:inline-block;font-weight:700;color:#1e6aeb}.btn-active-text .text:after{position:absolute;bottom:-5px;display:block;width:100%;content:' ';border-bottom:2px solid #2e7fff}.angle-btn{position:relative;padding:1px;background:#fff;border:1px solid #c4c4c4;border-right:none}.angle-btn:first-child{border-radius:2px 0 0 2px}.btn-toolbar>.angle-btn{margin-right:8px}.angle-btn:after,.angle-btn:before{position:absolute;top:-1px;right:-8px;display:block;width:0;height:0;content:' ';border-color:transparent transparent transparent #c4c4c4;border-style:solid;border-width:17px 0 17px 8px}.angle-btn:after{right:-7px;border-color:transparent transparent transparent #fff;border-radius:2px}.angle-btn .btn{padding:6px;font-weight:700;background:#fff;border:none;border-radius:4px!important}.angle-btn .btn.btn-limit{padding-right:16px}.angle-btn .btn.btn-limit>.caret{right:4px}.angle-btn+.angle-btn{border-left:none}.angle-btn+.angle-btn>.btn-group:first-child{padding-left:8px}.angle-btn+.angle-btn>.btn-group:first-child:after,.angle-btn+.angle-btn>.btn-group:first-child:before{position:absolute;top:-2px;left:0;display:block;width:0;height:0;content:' ';border-color:transparent transparent transparent #c4c4c4;border-style:solid;border-width:17px 0 17px 8px}.angle-btn+.angle-btn>.btn-group:first-child:after{left:-1px;border-color:transparent transparent transparent #fff;border-width:17px 0 17px 8px}.btn-toolbar>.angle-btn.active,.btn-toolbar>.angle-btn:last-child{border-color:#2e7fff}.btn-toolbar>.angle-btn.active .btn,.btn-toolbar>.angle-btn:last-child .btn{color:#2e7fff}.btn-toolbar>.angle-btn.active:after,.btn-toolbar>.angle-btn.active:before,.btn-toolbar>.angle-btn:last-child:after,.btn-toolbar>.angle-btn:last-child:before{border-color:transparent transparent transparent #2e7fff}.btn-toolbar>.angle-btn.active:after,.btn-toolbar>.angle-btn:last-child:after{border-color:transparent transparent transparent #fff}.btn-toolbar>.angle-btn+.angle-btn:last-child>.btn-group:first-child:after,.btn-toolbar>.angle-btn+.angle-btn:last-child>.btn-group:first-child:before,.btn-toolbar>.angle-btn.active+.angle-btn>.btn-group:first-child:after,.btn-toolbar>.angle-btn.active+.angle-btn>.btn-group:first-child:before{border-color:transparent transparent transparent #2e7fff}.btn-toolbar>.angle-btn+.angle-btn:last-child>.btn-group:first-child:after,.btn-toolbar>.angle-btn.active+.angle-btn>.btn-group:first-child:after{border-color:transparent transparent transparent #fff}.btn-toolbar>.angle-btn.active+.angle-btn,.btn-toolbar>.angle-btn.normal{border-color:#c4c4c4}.btn-toolbar>.angle-btn.active+.angle-btn .btn,.btn-toolbar>.angle-btn.normal .btn{color:#313c52}.btn-toolbar>.angle-btn.active+.angle-btn:after,.btn-toolbar>.angle-btn.active+.angle-btn:before,.btn-toolbar>.angle-btn.normal:after,.btn-toolbar>.angle-btn.normal:before{border-color:transparent transparent transparent #c4c4c4}.btn-toolbar>.angle-btn.active+.angle-btn:after,.btn-toolbar>.angle-btn.normal:after{border-color:transparent transparent transparent #fff}.btn-toolbar>.angle-btn.active+.angle-btn>.btn-group:first-child:before,.btn-toolbar>.angle-btn.normal>.btn-group:first-child:before{border-color:transparent transparent transparent #c4c4c4!important}.nav>li>.btn.btn-primary{color:#fff}.nav>li>.btn.btn-primary:focus,.nav>li>.btn.btn-primary:hover{background:rgba(46,127,255,.8)}.btn.btn-action,.c-actions .btn{display:inline-block;width:26px;padding:2px;overflow:hidden;line-height:20px;color:#18a6fd;background:0 0;border-color:transparent}.btn.btn-action>i,.c-actions .btn>i{position:relative;top:1px;font-size:18px}.btn.btn-action:hover,.c-actions .btn:hover{color:#2e7fff;background-color:#cce1ff}.c-actions .btn+.btn{margin-left:-4px}.label{position:relative;display:inline-block;padding:3px 5px;font-size:12px;font-weight:400;vertical-align:middle;border-radius:2px}.label+.label{margin-left:4px}.label-pale{background:#b3d1ff!important}.label-badge{border-radius:9px}.label-light{color:#3c4353;background-color:#ddd}.label-primary{background:#2e7fff!important}.label-gray{color:#878da0;background:#e8ebef}.label-outline.label-danger{color:#fc5959;background:#ffebeb;border-color:rgba(252,89,89,.25)}.label-outline.label-light{color:#838a9d;background:#f2f5fb;border-color:#e1e5ee}.label-primary.label-outline{background:#e6f0ff!important;border-color:rgba(46,127,255,.25)}.label-outline.label-success{background:#ecfff8;border-color:rgba(34,201,141,.25)}.label-outline.label-info{background:#e7f6ff;border-color:rgba(55,178,254,.25)}.label-outline.label-warning{background:#fff4ea;border-color:rgba(255,159,70,.25)}.label-dot{position:relative;top:-1px;padding:0;border-radius:50%}.label-dot+.status-text{display:inline-block;margin-left:5px}.label-icon{min-width:18px;padding:0;line-height:18px;border-radius:10px}.label-id{display:inline-block;min-width:30px;padding:0 5px;font-size:12px;line-height:16px;color:#838a9d;text-align:center;vertical-align:middle;background-color:transparent;border:1px solid #838a9d;border-radius:2px}.pri-1,.todo-pri-1{color:#fc5959}[class*=" status-"],[class^=status-]{color:#313c52}.status-changed,.status-delayed,.status-doing,.status-fail,.status-investigate{color:#fc5959}.status-changed>.label-dot,.status-delayed>.label-dot,.status-doing>.label-dot,.status-fail>.label-dot,.status-investigate>.label-dot{background-color:#fc5959}.status-wait{color:#838a9d}.status-wait>.label-dot{background-color:#7ec5ff}.status-unclosed{color:#838a9d}.status-unclosed>.label-dot{background-color:#2e7fff}.status-done,.status-normal,.status-pass,.status-resolved{color:#43a047}.status-done>.label-dot,.status-normal>.label-dot,.status-pass>.label-dot,.status-resolved>.label-dot{background-color:#22c98d}.status-postpone{color:#838a9d}.status-postpone>.label-dot{background-color:#fc5959}.status-blocked{position:relative;left:-5px;display:inline-block;padding:0 5px;line-height:20px;color:#313c52;background:0 0;border-radius:10px}.status-blocked>.label-dot{background-color:#ff9f46}.status-pause,.status-suspended{color:#ff9f46}.status-pause>.label-dot,.status-suspended>.label-dot{background-color:#ff9f46}.status-active.status-bug,.status-draft{color:#8166ee}.status-active.status-bug>.label-dot,.status-draft>.label-dot{background-color:#8166ee}.status-closed,.status-terminate{color:#838a9d}.status-closed>.label-dot,.status-terminate>.label-dot{background-color:#838a9d}.status-cancel{color:#838a9d}.status-cancel>.label-dot{background-color:#c4c4c4}.label-pri{display:inline-block;min-width:18px;max-width:100%;height:18px;padding:0 4px;overflow:hidden;font-size:12px;line-height:16px;color:#838a9d;text-align:center;text-overflow:clip;white-space:normal;vertical-align:middle;border:1px solid #838a9d;border-radius:10px}.label-pri-1,.label-selector>.label-pri[data-value="1"]{overflow:visible;color:#ff6f42;border-color:#ff6f42}.label-pri-2,.label-selector>.label-pri[data-value="2"]{color:#f38f19;border-color:#f38f19}.label-pri-3,.label-selector>.label-pri[data-value="3"]{color:#37b2fe;border-color:#37b2fe}.label-pri-4,.label-selector>.label-pri[data-value="4"]{color:#22c98d;border-color:#22c98d}.label-pri-5,.label-selector>.label-pri[data-value="5"]{color:#838a9d;border-color:#838a9d}.label-pri-0,.label-selector>.label-pri.active[data-value="0"]{color:#d5d9df;border-color:#d5d9df}.label-severity{position:relative;display:inline-block;width:24px;height:20px;font-weight:bolder;text-align:center;vertical-align:middle}.label-severity:before{position:absolute;top:-3px;left:0;z-index:0;display:block;font-family:ZentaoIcon;font-size:14px;font-size:24px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;color:inherit;text-transform:none;content:"\e973";speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.label-severity:after{position:absolute;top:7px;left:0;z-index:1;display:block;width:24px;font-size:12px;line-height:12px;text-align:center;content:attr(data-severity)}.label-severity[data-severity],.label-severity[data-value]{color:#ccc}.label-severity[data-severity="0"],.label-severity[data-value="0"]{color:#ccc}.label-severity[data-severity="1"],.label-severity[data-value="1"]{color:#fb2b2b}.label-severity[data-severity="2"],.label-severity[data-value="2"]{color:#c92d79}.label-severity[data-severity="3"],.label-severity[data-value="3"]{color:#f48116}.label-severity[data-severity="4"],.label-severity[data-value="4"]{color:#ffba34}.label-severity[data-severity="5"],.label-severity[data-value="5"]{color:#9ea3b0}.label-severity-custom[data-severity]{color:#d5d9df}.label-severity-custom[data-severity="1"]{color:#c62828}.label-severity-custom[data-severity="2"]{color:#ff8f00}.label-severity-custom[data-severity="3"]{color:#fdd835}.label-severity-custom[data-severity="4"]{color:#cddc39}.label-severity-custom[data-severity="5"]{color:#8bc34a}.label-selector{padding:0 10px}.label-selector>.label{display:inline-block;min-width:24px;height:24px;padding:0 5px;font-size:14px;line-height:20px;text-align:center;cursor:pointer;background:0 0;border:2px solid #d5d9df;border-radius:15px}.label-selector>.label+.label{margin-left:10px}.label-selector>.label.empty{border-color:transparent}.label-selector>.label.label-severity{font-size:12px;line-height:28px;border-color:transparent}.label-selector>.label.label-severity:before{top:-2px;left:-2px}.label-selector>.label.label-severity:after{display:none}.label-selector>.label.label-severity.active{background:0 0;filter:none;-webkit-filter:none}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{margin-top:2px}.ie .checkbox input[type=checkbox],.ie .checkbox-inline input[type=checkbox],.ie .radio input[type=radio],.ie .radio-inline input[type=radio]{margin-top:4px}.is-firefox .checkbox input[type=checkbox],.is-firefox .checkbox-inline input[type=checkbox],.is-firefox .radio input[type=radio],.is-firefox .radio-inline input[type=radio]{margin-top:3px}.checkbox-primary,.radio-primary{position:relative;display:block;vertical-align:middle}.checkbox-primary.inline-block,.radio-primary.inline-block{display:inline-block}.checkbox-primary.inline-block+.inline-block,.radio-primary.inline-block+.inline-block{margin-left:15px}.checkbox-primary>input,.radio-primary>input{position:absolute;top:0;left:0;z-index:3;width:100%;height:100%;margin:0;opacity:0}.checkbox-primary>label,.radio-primary>label{display:block;height:20px;padding-left:20px;margin:0;font-weight:400;line-height:20px;cursor:pointer}.checkbox-primary>label:after,.checkbox-primary>label:before,.radio-primary>label:after,.radio-primary>label:before{position:absolute;top:3px;right:0;left:0;display:block;width:14px;height:14px;line-height:14px;text-align:center;content:' ';border-radius:3px}.checkbox-primary>label:after,.radio-primary>label:after{z-index:1;border:1px solid #d9dbe1;border-color:rgba(0,0,0,.15);-webkit-transition:.4s cubic-bezier(.175,.885,.32,1);-o-transition:.4s cubic-bezier(.175,.885,.32,1);transition:.4s cubic-bezier(.175,.885,.32,1);-webkit-transition-property:border,background-color;-o-transition-property:border,background-color;transition-property:border,background-color}.checkbox-primary>label:before,.radio-primary>label:before{top:3px;z-index:2;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-weight:900;font-variant:normal;line-height:1;text-transform:none;content:"\e5ca";opacity:0;-webkit-transition:.2s cubic-bezier(.175,.885,.32,1);-o-transition:.2s cubic-bezier(.175,.885,.32,1);transition:.2s cubic-bezier(.175,.885,.32,1);-webkit-transition-property:opacity,-webkit-transform;-o-transition-property:opacity,-o-transform;transition-property:opacity,-webkit-transform;transition-property:opacity,transform;transition-property:opacity,transform,-webkit-transform,-o-transform;-webkit-transform:scale(0);-ms-transform:scale(0);-o-transform:scale(0);transform:scale(0);speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.checkbox-primary.checked>label:after,.checkbox-primary>input:checked+label:after,.radio-primary.checked>label:after,.radio-primary>input:checked+label:after{background-color:#22c98d;border-color:#22c98d;border-width:4px}.checkbox-primary.checked>label:before,.checkbox-primary>input:checked+label:before,.radio-primary.checked>label:before,.radio-primary>input:checked+label:before{color:#fff;opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}.checkbox-primary input:indeterminate+label:before,.checkbox-primary.indeterminate>label:before,.radio-primary input:indeterminate+label:before,.radio-primary.indeterminate>label:before{top:9px;left:3px;width:8px;height:2px;content:' ';background-color:#a3a2bc;opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}.checkbox-primary.focus>label:after,.checkbox-primary>input:focus+label:after,.radio-primary.focus>label:after,.radio-primary>input:focus+label:after{border-color:#22c98d;-webkit-box-shadow:0 0 0 3px rgba(34,201,141,.2);box-shadow:0 0 0 3px rgba(34,201,141,.2)}.checkbox-primary:hover>label:after,.radio-primary:hover>label:after{border-color:#22c98d}.checkbox-primary.checkbox-right>label,.radio-primary.checkbox-right>label{padding:0 30px 0 0}.checkbox-primary.checkbox-right>label:after,.checkbox-primary.checkbox-right>label:before,.radio-primary.checkbox-right>label:after,.radio-primary.checkbox-right>label:before{right:0;left:auto}.checkbox-primary input:disabled+label:after,.checkbox-primary.disabled>label:after,.radio-primary input:disabled+label:after,.radio-primary.disabled>label:after{background-color:#e5e5e5!important;border-color:#bbb!important}.checkbox-primary input:disabled:checked+label:after,.checkbox-primary.checked.disabled>label:after,.radio-primary input:disabled:checked+label:after,.radio-primary.checked.disabled>label:after{background-color:#bbb!important}.radio-primary>label:after{border-radius:50%}.radio-primary>label:before{top:7px;left:6px;width:6px;height:6px;content:' ';border:none;border-radius:50%}.radio-primary.checked>label:after,.radio-primary>input:checked+label:after{background-color:transparent;border-color:#22c98d;border-width:2px}.radio-primary.checked>label:before,.radio-primary>input:checked+label:before{background-color:#22c98d}.radio-primary input:disabled:checked+label:after,.radio-primary.checked.disabled>label:after{background-color:transparent;border-color:#bbb}.radio-primary input:disabled:checked+label:before,.radio-primary.checked.disabled>label:before{background-color:#bbb}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li>a{position:relative;border:none!important;border-radius:2px!important}.nav-tabs>li.active>a{font-weight:700;color:#313c52!important}.nav-tabs>li.active>a:before{position:absolute;right:0;bottom:-1px;left:0;display:block;height:2px;content:' ';background:#2e7fff}.nav-tabs-primary>li.active>a{color:#1e6aeb!important}.panel{position:relative;margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045);box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045)}.panel-body{padding:20px}.panel-body.has-table{padding:10px}.panel-body.has-table .table{margin-bottom:0;table-layout:fixed}.panel-heading{padding:12px 48px 12px 20px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading+.panel-body{padding-top:0}.panel-title{font-size:14px;font-weight:700;line-height:20px}.panel-title .label{top:-1px}.panel-actions{position:absolute;top:0;right:0;padding:7px 8px}.panel:hover .panel-actions{z-index:10}.panel-actions>li>a{display:inline-block;min-width:30px;padding:0 5px;line-height:30px;color:#a6aab8;text-align:center;border-radius:4px}.panel-actions>li>a:hover{color:#313c52;text-decoration:initial;background-color:#f1f1f1}.panel-actions .btn-icon{color:#a6aab8}.panel-actions .btn.text-primary{color:#2e7fff}.panel .empty-tip{padding:30px 10px 50px;font-size:14px;color:#838a9d;text-align:center}.progress-text-left{position:relative;margin:7px 0;margin-left:35px;overflow:visible}.progress-text-left .progress-text{position:absolute;top:-7px;left:-35px;display:block;width:35px;height:20px;padding-right:5px;line-height:20px;color:#838a9d;text-align:right}.chart-color{width:20px}.chart-color-dot{display:inline-block;width:10px;height:10px;border-radius:50%}.chart-row{margin-top:10px}.chart-row+.chart-row{padding-top:10px;border-top:1px solid #eee}.chart-wrapper{padding:10px 5px;background:#eee}.chart-wrapper>h4{margin:5px 0 10px}.table-wrapper{max-height:250px;overflow:auto}.table-wrapper .table{margin:0}.progress-pie{position:relative}.progress-pie canvas{display:block}.progress-pie .progress-info{position:absolute;top:0;left:0;width:100%;height:100%;padding-top:25px;text-align:center}.progress-pie .progress-info>small{display:block;line-height:14px;color:#a6aab8}.progress-pie .progress-info>strong{display:block;font-size:36px;line-height:40px}.progress-pie .progress-info>strong>small{font-size:20px}.progress-pie-120 .progress-info{padding-top:30px}.progress-pie-120 .progress-info>small{line-height:18px}.progress-pie-50 .progress-info{padding-top:4px}.progress-pie-50 .progress-info>strong{font-size:20px;font-weight:400}.progress-pie-50 .progress-info>strong>small{font-size:14px}.progress-pie[data-value="100"] .progress-info>strong{-webkit-transform:scale(.7);-ms-transform:scale(.7);-o-transform:scale(.7);transform:scale(.7)}.progress-pie-24 .progress-info{right:-10px;left:-10px;width:auto;padding-top:0;font-size:12px;line-height:24px;-webkit-transform:scale(.9,1);-ms-transform:scale(.9,1);-o-transform:scale(.9,1);transform:scale(.9,1)}.progress-pie-24[data-value="100"] .progress-info{-webkit-transform:scale(.8,1);-ms-transform:scale(.8,1);-o-transform:scale(.8,1);transform:scale(.8,1)}.progress-pie-26 .progress-info{right:-10px;left:-10px;width:auto;padding-top:0;font-size:12px;line-height:26px;-webkit-transform:scale(.9,1);-ms-transform:scale(.9,1);-o-transform:scale(.9,1);transform:scale(.9,1)}.progress-pie-26[data-value="100"] .progress-info{-webkit-transform:scale(.8,1);-ms-transform:scale(.8,1);-o-transform:scale(.8,1);transform:scale(.8,1)}.status-bars{display:table;width:100%;height:140px;padding:5px;padding-top:50px;margin:0;overflow:hidden}.status-bars>li{position:relative;display:table-cell;text-align:center;vertical-align:bottom}.status-bars .bar{position:absolute;bottom:20px;left:50%;display:block;width:10px;margin-left:-5px;background:#2e7fff;border-radius:5px 5px 0 0}.status-bars .bar:after{position:absolute;right:-50px;bottom:0;left:-50px;display:block;height:1px;content:' ';background:#eee}.status-bars .title{font-size:12px;font-weight:400;color:#a6a8b6}.status-bars .value{position:relative;top:-20px;left:-20px;display:inline-block;width:50px;font-size:16px;font-weight:700;text-align:center}.status-bars-h{display:block;padding-right:50px;padding-left:60px;list-style:none}.status-bars-h>li{position:relative;height:40px;border-left:1px solid #eee}.status-bars-h .bar{position:relative;top:15px;display:block;height:10px;line-height:20px;background:#2e7fff;border-radius:0 5px 5px 0}.status-bars-h .title{position:absolute;top:-5px;left:-60px;width:60px;padding-right:10px;font-size:12px;color:#a6a8b6;text-align:right}.status-bars-h .value{position:absolute;top:-5px;right:-50px;display:block;width:40px;font-size:14px;font-weight:700;text-align:left;white-space:nowrap}.ring,[class*=" ring-"],[class^=ring-]{width:24px;height:24px;font-size:12px;line-height:24px;text-align:center;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAACZAAAAAwCAMAAACyyT3hAAAAP1BMVEUAAAAAAAA5rm06sG87snE4q200o2o7sG86r244q2w6r244rWw7snA7sG86rG05rm07snA5qWw7sW86sG48s3ECZypFAAAAFHRSTlMACl2b8TkPtnsqj0fVv4Zt4RrIqxUkbyYAAA1gSURBVHja7NrbbtswEIRhmyJ1IiVS0v/+z9rCbeGkdZodNEnVdHmTxP4wmF1exLB9eXCu12tqLX39cTGcm78use2z5Msx19wLfunGpOZvSv4xh/798tt+dKPSZ+jGMNn9/pWnXp1X7f9e+ynf53Xv3r37j/bLyfq4/1u+n8T8VI8jLWY/hbEbSjT76+2ssH77zeyBJHooom9dkHzssuQ5wvau+YuWz141/87zMoTewP9kP+7d2/31ZPln82e7L/fuP9LnJPk+DDDb/TQvEaDc/1//vk9Yvrl7vmHa+ztk347BXwcA1UMRPXCIfq+b5Jm3d82PneY5guSHG1fmVfu/837m5N69e/fu3T89fRZ9bRTBpyMCLFa/zfG7CU98l1/M/+EBS/59PT//bfD9lCS/pbAeR7bnb1OqC7AI+QWAkiz5Kdd1B6BVLb8Fk891Poox/7bPUNcIwGHrM3YDAHu1zyv0yWHcjfNen++nWvsvANC5d6/468nyz+bPdl/u3es+NFbFjw1A8BGAPUv5xGFMz/382HcAsNy+W9Sb9/PwwbP4ra5J8WluQDb7qQ4Ayybl05nnrQVgSFafB4AyWfvPEaBT57X2Seu3ecX9HBfN75t79+7du/+sPnVBy++A1e6nHaAEsz9ufk7W/gUgrvmBP/pf/bYDtDlp+/x8j4du1XwBFsHvQMlW348NIJjz0yH5bbz1n7R5m71/bUAL2n5iMueHAYjZvXv37t1/Tp8bJMFPDYhCfgTa2Ft9aEDJ5vzUbvnbC3562GcI5j6vf3T43/h+hlnJnwFGs99WgGDPv/lq9yPQsjCv1mcT/aUDGEVf3bt37979v+DH1kk+RIiCrwCz0AeI82bvc8vvzT5HYP1NfnjQv3uL/d+f/X/8ljUfGiD4ChDsPkcgCX0iUMX8UfDDw/6Xl+fV+q8Awb373/rLyfLP5s92X+4/rQ8QFV+BmO0+AyTND0nqT1H6xFf715/zWe35l5fXf3/e/YtnGugUnxsQhD47sNl9Klp+ipq/DmL/ovXPAJN79+7du/9on5dV80An+BEoye4nYLhofu/tPgAtafO26RWfn/chv9F93YX7N/PTDoOSHzW/LVA2Ib9Am+y+34Es5DdYlHmB2Lt37969+4/1fYQs+KlB6e0+Acsm9CnQXTQfJ61PTMI+dyiv99me7pO3u6+7cf9mvj/Ikgc6xRfNXyIcgt8KLIoX+0/A4d69e/fu/8znoPkOBsH3O8Qk5C8wiH12xR9AFvwAUfEzREOf/Wmf6Q3v967cv52fNJ+BIPgg+gmogk/xmb9a+lex/+ze/WN/PVn+2fzZ7sv93/MVVsUHIAn+AKrgV4hK/gxsYv+q7Ycg+AxMFt/d+9vy/QXZP+RHCIrvoGyCr9B6wa/QNq3/ftHyye7df2HP3pbQBKIgihqGmzKA4P7/b02lTOX2EncSEky1z8uungPosYyPj/8Nv8Mk/HaHm/Adzk/AIvy1hUb4vkKR5y0yf33Nd+K89vo+Xfy/81Oj/FahmPwBRuH7CjeTX6GRfUp8fHx8/Fc/UpRvoBpf4L4ZL/MfsLvzUoxfgM6e1/V5vNjn9jl/eLV/FrL/2DcwS1+lZxN+9X3a+Pj4+Pgvvm9B5Q8wyvxV+K11+VdgMv2r810LRZ63MX1m6IwX+ep+eMr49/HL3ChfoTF+eD65r/q+wiTy+zus8fHf+8vJ8s/mz3a94n/im075FarxE7Sby2975+8mf5T9G9gvLp/O9Jf5nfHNcz6vzzMLWfyX1wiz8Ss8ZP5g/A32+Pj4+P/XP0D5GRbjC9xkfjF+l/lV9h9gtP2PnOfN9JntfMQPsCeN/3/9dmc2/gqtym+d74BrfHx8/P/q+xaM34CryZf+CnTCT9Ca/A7YnG+N71V/P89La/r7eZ5pIYj/x35aNuUH6IzfpZ+hiY+Pj38X35dF+Ql241eoxncwGD/K/EbmL8r7/En2X+X8J99/dveb+4L/EB//zX+Wo/EFVunH+Pj4+HfxI0zG3+BmfIHF+AaKzB/VeU+Wv8BwZJ/14P56IbvEx39+bdDIBa4YP8bHx8e/kS/QGb/DZPwDVuMXGGV+Y3yR+QM+X/py5HlvB/e3C1l8/Fe/TYZfJtjNGxoY4uN/8JeT5Z/Nn+16vbVf7kX5Ga7GV+iM32Ey/gHNkfnDyfJnWA+d58H9L+aB/4Tj43/Vb4DxHdT4+Pj4f+WhVb7CZnwrfYWr8Tt0xt9lfj1ZfoVJedvn4P5ZyOL/nn+wGN9DGx8fH/+vPKB8630f/0f9drI+ymchiz+vB5Q/4Qd6fHz8efxCycIU/5G9e9lRHAaAKEr7kZi8nIT7/986UlatWaWGCbjpYn1UKizAQU7sZr0vyOzb9QkUv8Ek+qT4u77ksYv5q5g/tZQv91F9g0ta9j/Loy8RZnv7795LlvYf4b/U/JgWccLur/xBH2EXfbw4v28pP8B8cZ9HgzfR27/Tj9utpZvK7e2f8W09tWdv/8S+aBmi4OWnPis8FF+gKD7o+VHM78X8QfGD3uch+qG1bQ7s3+sDbIqPUEVf7O2/+5a2vWhqHyx7+++vUbwAGsQJfhEnjAhB8UXPLy3l97BcOgF7Y2H7v3yBeuVGqcXe/oW+qaN77O2f8jF2it9hVPwE9eKjpYqYH0S/XJk/ieM5i+P5gLG1o3Ls/7PfllE9Gkj0u3r0jb39q3xbE6q9/Qs9pE7wd+BKn4FNyU8wCn4DsuKT5vPF49Phw+k/389Mm+IT3DWf5MOh7e1f5G/n1ywPaW//MR4eil9EP0AU83sxf1d8Ub3YJ4q+iONZYRDHZ7b/aX6GovgIVfG9kr8ffYbz+fb2z/nbTZnw7O2f8V9N9clV8hMEwW8JRsF3q5i/QlX676rX+mSgqPmKnyGr42n/dh/mRfEjTIoP0Kv53Vlfj/z1tA/29s/5039hDmdv/2s9pE7wBXolv+r5aRP8CIh+UvpHSFnwQXy/GeabmG//fj9DUPyk+Q0IYp+q+ATB3v4lvqkJz96+ZR+pip8gCH5bIUr5ot/hofgIRfAZWK58vzMUxfcw2F/hQ1D8ANMm+Aq9mt8JPsB0Mj8e+UIfe/vn/Olv5KHs7e3P+QC74gdY75onC34BRsFXSEqfCFOn+XUT/KDnk+0v8AGK4LcJouC7FQYlf4VF6b9DEb4vmfP97X+HX859fvT843Vq880D2dv/o7811udyv0AV/IjmK/AQfE6wKB4YxD5B8AGoYp9R8AsQ7f+0a287bsJQFIapTxxssA3/+z9rJVS1VVOmezeDOp1s7mI+LS1bxJZChH7WeAdkpXcKX4Gs8yHqPFXyfC4/jspNlG/+VbyTPD/6/G+X9EAyb9681Dun8SMcCh8D7Enu0wirpk/T+QyMGh90fgiwKPvs5oU+45PCH4DGeyjK/KDpv0Lpyv7z237mp+/jDmRBvvnX8YLn5y/yxd/gU5g3b/4enyF0uU8FWpT7vgIKf56JSe5jgNY1XtlnV+5XQOjmhf6AfVb4AkGR3wuMXe7nBrsiPwN7evf+3xN7AAR9zJt/yst3uPO+efPmb/LbmhXeAwofCzAp8kcgyn0qyj4NcHLflfkzr+494yz3DmhZ7mdgH3T5JSp91/lxeL/+GWC++HyZb/6lfL72+Yl80d8OztvmzYv8I/9gff5/H6DKfQ6ofARwCj+i61MAp+sfstw7gPrZfO4KDzQn9w5A4TMQZrmvQFDkT0DIuv5j1Hku87eH1aiC9TH/Yn678tsT+Vdn5uM98+bNfxDvwyT3KUBwcr/o9oe+BJVPZ76T529AyBf+8jz9bH6BJYn9BLDI8x3A1nX5VZ6fGzAp+yxJ6ZX9t+ufn6m/W5+3+pt/Oe+vnx99/tWxYOM2buOfanyGluU+AHuX+jQ1IERxfi5Ac1Lfz/wmzk9uBML8P/jNO7lfgSL3sQC73DuANiWpzwA+i/Pns//UxflNN1/XgFaTuE8D1sv8/OADMLrLfPPmn/JXO/qX3w+aN2/+P/X1iHK/AZM43xWAsQt9XE5/zEKftwBQoji/nfnpH/m41S72GQg+Cn1aAcZpluYvqPLjCFCqdL59B1jFfvAAYZH2j8r5dq+b77Bd+70/8nQAtOX3+ebNP+Wvd9zz+vWjefNaP3ywPuZFfqNluQcIm9DnndNPIh+nMQDQJL5HN/kVAJY/+TinJPX5OCYXhX4F1hrnPgh8LAAUaX5tnH6rWdTfNXX+yZeaRd4DELaaZetZdwBGf/N8Jyfz+Wp9/Buv8mF97G/e/JP+egt6vAbz5s2/is9J4QthEvsVIBxR5F2A0y9d4Avwzfv8Jw8g9gXk/gDkvtdd5xfuzfdB5d2Iyg9uvHe+PrzD+izxyueLfPPmn/OaLXoYzJv/az98sD7m39XHrPAe1pqEfgUI6zJL/AYAsNc/94mA3O+g8M43lc8+aPxcR+7M73VH5aei7cOd8011fW59Vve2f1wf8+bv84+vmsybN2/+Xfzc5b4fR43i/DWU0U9uFvlpb0HsU578WOR+iJNfFb4r85Nbjjvz58kfe1N4p+wf3Xbsyvmq+9+4Ps68+dv8V5yOdlOUBd3vAAAAAElFTkSuQmCC);background-position:0 0;-webkit-background-size:1224px 24px;background-size:1224px 24px}.ring>span,[class*=" ring-"]>span,[class^=ring-]>span{display:inline-block;-webkit-transform:scale(.9,1);-ms-transform:scale(.9,1);-o-transform:scale(.9,1);transform:scale(.9,1)}.messager{border-radius:4px;-webkit-box-shadow:0 4px 16px rgba(0,0,0,.2),0 2px 8px rgba(0,0,0,.1);box-shadow:0 4px 16px rgba(0,0,0,.2),0 2px 8px rgba(0,0,0,.1)}.messager-icon{vertical-align:middle}.messager-icon>.icon{font-size:24px}.messager-content{padding:18px 20px;font-size:18px;line-height:30px}.messager-content>.icon{font-size:28px;line-height:30px}.messager-actions{vertical-align:middle}.messagger-zt{color:#313c52;background-color:#fff!important}.messagger-zt .messager-icon>.icon{color:#2e7fff}.messagger-zt .messager-actions>.action{color:#838a9d}.messagger-zt.messager-success .messager-icon>.icon{color:#22c98d}.messagger-zt.messager-danger .messager-icon>.icon{color:#fc5959}.messagger-zt.messager-warning .messager-icon>.icon{color:#ff9f46}.messagger-zt.messager-info .messager-icon>.icon{color:#37b2fe}.tree{padding-left:0;overflow:hidden}.tree ul{position:relative;display:none;padding-left:0}.tree li{position:relative;padding:2px 0 2px 15px;list-style:none}.tree li.heading{padding-left:5px;color:#313c52}.tree li>a{display:block;max-width:90%;padding:2px 6px;color:#313c52;word-break:break-all}.tree li>a:hover{color:#313c52}.tree li>a.tree-toggle:hover{background:0 0}.tree li.active>a{position:relative;font-weight:700;color:#2e7fff}.tree li>.list-toggle{position:absolute;top:1px;left:1px;z-index:10;width:20px;font-size:14px;line-height:22px;color:#cbd0db;text-align:center;cursor:pointer;-webkit-transition:all .2s;-o-transition:all .2s;transition:all .2s}.tree li>.list-toggle:before{content:"\f0da"}.tree li>.list-toggle:active,.tree li>.list-toggle:hover{color:#2e7fff}.tree li.has-active-item>.list-toggle{color:#313c52}.tree li.has-list.open>ul{display:block}.tree li.has-list.open>.list-toggle{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.tree li.has-list.open:before{position:absolute;top:16px;bottom:-5px;left:10px;display:block;content:' ';border-left:1px solid #d8d8d8}.tree-actions{display:inline-block;margin-left:5px;vertical-align:middle}.tree-actions a{display:inline-block;margin-left:5px;font-size:13px;opacity:.6}.tree-actions a:hover{opacity:1}.tree li>.module-name{color:#313c52;vertical-align:middle}.tree li>.module-name:hover{background-color:#f0f2f5}.tree li>.module-name:hover>a{color:#313c52}.treemap-node-fold-icon:before{position:relative;left:-4px;min-width:18px}.dropdown-menu{padding:5px 0;border-color:rgba(0,0,0,.1)}.dropdown-menu>li{padding:0 10px}.dropdown-menu>li>a{padding:2px 10px;margin:5px 0;border-radius:3px}.dropdown-menu>li>a>.icon{position:relative;left:-5px;opacity:.5}.dropdown-menu>li>a:hover>.icon{opacity:.8}.dropdown-menu>li.active>a,.dropdown-menu>li.selected>a{position:relative;color:#fff;background-color:#18a6fd}.dropdown-menu>li.selected>a:after{position:absolute;top:2px;right:4px;display:block;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;line-height:20px;text-transform:none;content:"\e5ca";speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.dropdown-menu>li.divider{margin:10px}.dropdown-submenu>a:after{margin-right:-5px}.dropdown-submenu>.dropdown-menu.pull-left{margin-left:-1px}.dropdown-submenu:focus>a,.dropdown-submenu:hover>a{color:#313c52;background-color:#e6f0ff}.dropdown-submenu:hover>a:after{border-left-color:#2e7fff}.dropdown-submenu>a:hover:after{border-left-color:#fff}.pager .btn{padding:3px 10px}.pager .btn .caret{opacity:.7}.pager>li>.pager-label{padding:2px;line-height:20px}.pager>li>.pager-item{min-width:20px;padding:1px;margin:2px 0;font-size:16px;line-height:20px;text-align:center;background:0 0;border-color:transparent}.pager>li>.pager-item:hover{background-color:rgba(0,0,0,.1)}.pager>li>.pager-item>.icon{position:relative;top:-1px}.pager>li>.btn:hover,.pager>li>a:hover{background:rgba(0,0,0,.1)}.pager>li.disabled>a.pager-item{background:0 0;border-color:transparent;opacity:.5}.pager>li.active>a{background-color:#18a6fd}.pager>li .btn-group .btn{padding:1px;margin:1px 0;border-radius:4px}.pager .dropdown-menu{width:200px}.pager .dropdown-menu>li{float:left;width:33.333333%}.modal-dialog{width:900px;max-width:1360px;border:none;border-radius:0;-webkit-box-shadow:0 0 20px 0 rgba(0,0,0,.25);box-shadow:0 0 20px 0 rgba(0,0,0,.25)}.modal-dialog.modal-md{width:700px}.modal-dialog.modal-xs{width:400px}.modal-dialog.modal-sm{width:500px}.modal-dialog.modal-lg{width:1200px}.modal-dialog.modal-fullscreen{position:fixed;max-width:initial}.modal-header{padding:20px 0;margin:0 20px}.modal-header>.close{color:#838a9d;text-shadow:0 1px 0 rgba(255,255,255,.85);opacity:1}.modal-header>.close:hover{color:#0b0f18}.modal-footer{padding:20px 0;margin:0 20px}.modal-title{font-size:14px;font-weight:400;line-height:20px}.modal-actions{position:absolute;top:16px;right:16px}.modal-actions .divider{position:relative;top:5px;display:inline-block;width:0;height:20px;margin:0 10px;border-left:#eee 1px solid}.modal-actions>.dropdown{display:inline-block}.modal-body{padding:20px}.modal-iframe .modal-body>iframe{border-radius:0}.modal.fade .modal-dialog{-webkit-transform:translate(0,-3%);-ms-transform:translate(0,-3%);-o-transform:translate(0,-3%);transform:translate(0,-3%)}.modal.fade.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-simple .modal-footer{padding-top:0;border-top:none}.modal-iframe .modal-header{position:relative;z-index:10;min-height:0;padding:0;border:none}.modal-iframe .modal-title{display:none}.modal-iframe .modal-header .close{position:absolute;top:12px;right:10px;font-size:32px;font-weight:200}.modal-iframe .modal-dialog{overflow:hidden}.modal-inverse .modal-header>.close{color:rgba(255,255,255,.7);text-shadow:none}.modal-inverse .modal-header>.close:hover{color:#fff}.modal-scroll-inside>.modal-dialog{max-height:100%}.hide-modal-close .modal-iframe .modal-header .close{display:none}.tile{text-align:center}.tile-title{line-height:20px;color:#313c52}.tile-amount{font-size:32px;font-weight:700;line-height:56px}.timeline>li{position:relative;list-style:none}.timeline>li:before,.timeline>li>a:after,.timeline>li>div:after{position:absolute;left:-20px;display:block;width:15px;height:15px;content:' ';border-radius:50%}.timeline>li:before{top:12px;left:-16px;z-index:3;width:7px;height:7px;background-color:#c4c4c4;border:none;border:1px solid #c4c4c4}.timeline>li>a:after,.timeline>li>div:after{top:11px;left:-17px;z-index:3;width:9px;height:9px;background-color:#2e7fff;border-radius:50%;opacity:0}.timeline>li+li:after{position:absolute;top:-12px;bottom:20px;left:-13px;z-index:1;display:block;content:' ';border-left:1px solid #eee}.timeline>li.active>a:after,.timeline>li.active>div:after{opacity:1}.timeline>li.active:before{top:8px;left:-20px;width:15px;height:15px;background-color:rgba(46,127,255,.2);border:none}.timeline>li>a,.timeline>li>div{display:block;padding:5px;line-height:20px}.timeline>li.active>a{color:#313c52}.timeline-tag{position:absolute;top:5px;left:-115px;font-size:12px}.timeline-tag-left{padding-left:115px}.timeline-sm{font-size:12px}.timeline-sm>li:before,.timeline-sm>li>a:after,.timeline-sm>li>div:after{top:10px;left:-20px;width:11px;height:11px}.timeline-sm>li.active:before,.timeline-sm>li:before{top:10px;left:-18px;width:11px;height:11px;background:0 0;border:1px solid #eee}.timeline-sm>li>a,.timeline-sm>li>div{line-height:20px}.timeline-sm>li>a:after,.timeline-sm>li>div:after{top:13px;left:-15px;width:5px;height:5px}.form-control{-webkit-box-shadow:none;box-shadow:none}.form-horizontal .form-group>label{padding-right:0}.form-actions{margin-top:20px;margin-bottom:0}.form-actions .btn{margin-right:10px}form label{font-weight:400;color:#313c52}.form-group .btn+.btn{margin-left:5px}.table-form{margin-bottom:0;table-layout:fixed}.table-form>thead>tr>th.required:after{position:relative;top:3px;right:auto;left:4px;display:inline-block;vertical-align:middle}.table-form>tbody>tr>td,.table-form>tbody>tr>th,.table-form>tfoot>tr>td,.table-form>thead>tr>th{padding:7px;vertical-align:middle;border-bottom:none}.table-form>tfoot>tr>td{padding:20px 7px 10px}.table-form>tbody>tr>th{width:100px;font-weight:700;text-align:right}.table-form .input-group{width:100%}.chosen-container-single .chosen-single{position:relative}.chosen-container-single .chosen-single>span{height:20px;line-height:20px;word-break:break-all;white-space:normal}.chosen-container-single .chosen-single div b{position:relative;top:1px;color:#cbd0db;opacity:1}.chosen-container-single .chosen-search:before{top:8px;right:15px}.chosen-container-multi .chosen-choices li.search-choice{font-size:13px;background:#eee;border-color:#cbd0db;-webkit-box-shadow:none;box-shadow:none}.chosen-container-single .chosen-search input[type=text]{height:30px;padding:3px 25px 3px 5px}.chosen-container-single .chosen-search{padding:3px 10px 0}.chosen-container-single .chosen-single{overflow:visible}.chosen-container .chosen-results{max-height:245px;padding:10px}.chosen-container .chosen-results>li{border-radius:4px}.chosen-container .chosen-results li.highlighted em{color:#fff}.table-responsive .chosen-container .chosen-results{max-height:200px}.picker>.picker-selections>.picker-selection>.picker-selection-text{word-break:break-word}.chosen-container .chosen-drop.chosen-no-wrap>.chosen-results>li,.picker-drop-menu>.picker-option-list>.picker-option .picker-option-text{text-overflow:clip;white-space:nowrap!important}.picker-drop-menu>.picker-option-list>.picker-option .picker-option-text{display:block;overflow:hidden;word-break:break-word}.chosen-compact.chosen-container-single .chosen-single>.chosen-search{top:-2px;right:-1px;bottom:-1px;left:-1px;display:none;height:auto;padding:0;opacity:0}.chosen-compact.chosen-container-single .chosen-single>.chosen-search>input{height:31px;padding:5px 26px 5px 8px;font-size:inherit;line-height:20px}.chosen-compact.chosen-container-single .chosen-single>.chosen-search:before{right:8px}.datetimepicker{padding:10px}.datetimepicker td.day.today{background-color:#fc7272}.datetimepicker td.day.active{background-color:#18a6fd}.datetimepicker tfoot th,.datetimepicker thead th{color:#838a9d}.input-control .colorpicker{top:0;z-index:auto;opacity:1}.input-control .colorpicker .btn{padding:5px}.input-control .input-control-icon-right.btn{top:0}.colorpicker .dropdown-menu{min-width:232px;padding:5px 10px 10px 10px}.colorpicker .dropdown-menu>li{display:block;float:left;padding:5px}.colorpicker .dropdown-menu>li.heading{width:100%;margin-bottom:5px;font-size:16px;font-weight:700;text-align:left}.colorpicker .dropdown-menu>li.heading>.icon-close{position:relative;top:4px;float:right;cursor:pointer;opacity:.6}.colorpicker .dropdown-menu>li>a{position:relative;display:block;width:100%;height:100%;padding:0;margin:0;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-align:center;text-transform:none;border:1px solid transparent;border-radius:50%;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.colorpicker .dropdown-menu>li>a:before{position:absolute;top:0;display:block;width:100%;height:20px;line-height:18px}.colorpicker .dropdown-menu>li>a:hover{-webkit-box-shadow:0 1px 4px rgba(0,0,0,.25);box-shadow:0 1px 4px rgba(0,0,0,.25)}.colorpicker .dropdown-menu>li>a.active:before{font-size:14px;content:"\e5ca"}.colorpicker .dropdown-menu>li>a.empty{color:#666;background:#fff}.colorpicker .dropdown-menu>li>a.empty:before{content:"\e90d"}.colorpicker .btn{position:relative}.colorpicker .btn .color-bar{position:absolute;right:5px;bottom:3px;left:5px;height:3px}.colorpicker .btn .color-bar[style*='background: ']+.ic{position:relative;top:-2px}.colorpicker .btn .ic{color:#cbd0db}.colorpicker .btn:hover .ic{color:#838a9d}.input-group .colorpicker{z-index:3}.input-group .chosen-container{display:table-cell}.input-group-addon{border-right-width:0;border-left-width:0}.input-group-addon:first-child{border-left-width:1px}.input-group-addon:last-child{border-right-width:1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin:0}.input-group-cell{display:table-cell;width:1%;padding:0 12px;white-space:nowrap;vertical-align:middle}.ke-container{border-color:#dcdcdc!important;border-radius:2px!important}.ke-container.focus{border-color:#2e7fff!important}.ke-toolbar{border-color:#dcdcdc!important}.required:after{top:6px;right:-10px;font-size:20px}td.required:after{top:12px;right:-5px}.input-group>.chosen-container.required:after,.input-group>.input-control.required:after{top:1px;right:1px;z-index:2}.input-group.required .required:after{display:none}.file-input{position:relative;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.file-input .input-group{width:auto}.file-input .input-group>.input-group-cell:first-child{padding-right:0;padding-left:7px}.file-input input[type=file]{position:absolute;width:0;height:0;opacity:0}.file-input .file-title{display:inline-block;max-width:400px;overflow:hidden;text-overflow:unset;white-space:nowrap;vertical-align:middle}.file-input .file-editbox{min-width:200px;max-width:100%}.file-input .file-size{display:inline-block;vertical-align:middle}.edit .file-input-empty,.file-input-edit,.file-input-normal,.normal .file-input-empty{display:none}.edit .file-input-edit,.normal .file-input-normal{display:block}.edit .file-input-edit.input-group,.normal .file-input-normal.input-group{display:table}.edit .file-input-normal{display:none!important}.file-input-normal>.input-group-btn{width:auto}.input-group .chosen-container-active .chosen-choices{border-color:#2e7fff!important}.input-group .chosen-container{min-width:100px}.input-group .input-group-btn .btn>.icon{line-height:17px}.os-mac select.form-control{-webkit-appearance:none;background-image:url(data:image/gif;base64,R0lGODlhCQAFAIAAAMvQ2////yH5BAEAAAEALAAAAAAJAAUAAAIKhH+BGYoNGWxgFgA7);background-image:url(data:image/gif;base64,R0lGODlhBwAEAIAAAMvQ2////yH5BAEAAAEALAAAAAAHAAQAAAIIhA+BGWoNWSgAOw==);background-repeat:no-repeat;background-position:right 5px top 12px;-moz-appearance:none}input::-webkit-contacts-auto-fill-button{position:absolute;right:0;display:none!important;pointer-events:none;visibility:hidden}.chosen-choices.has-error,.chosen-single.has-error,.form-control.has-error{border-color:#fc5959!important;-webkit-box-shadow:0 0 6px #febdbd!important;box-shadow:0 0 6px #febdbd!important}.popover-success.popover-form-result{font-weight:700;color:#fff;background:#22c98d}.popover-success.popover-form-result.popover.right .arrow:after{border-right-color:#22c98d}textarea.kindeditor{padding:0!important}.form-unsaved{outline:2px solid #ff9f46;-webkit-box-shadow:0 1px 12px #ff9f46;box-shadow:0 1px 12px #ff9f46;-webkit-transition:all .5s;-o-transition:all .5s;transition:all .5s}select[multiple].picker-select,select[multiple][data-toggle=picker]{max-height:32px;visibility:hidden}.picker-selection-remove:before{font-size:18px}.picker-user-option{position:relative;padding-left:40px}.picker-user-option>.avatar{position:absolute;top:5px;left:10px}.picker-user-option>.picker-option-text{display:inline-block;min-width:40px;font-weight:700}.picker-user-option>.dept-name,.picker-user-option>.role-name{display:inline-block;margin-left:10px;font-size:12px;opacity:.6}.picker-user-option>.dept-name{min-width:50px}#mainHeader{height:50px;color:#fff;background:#3785ff;border-top-color:#2e7fff;border-bottom-color:#e6f0ff}#mainHeader>.container{min-width:1200px;padding:0}#heading{position:absolute;top:10px;left:20px}@media (min-width:1400px){#heading{left:40px}}#heading h1{float:left;max-width:250px;margin:0;overflow:hidden;font-size:20px;font-weight:400;line-height:30px;text-overflow:unset;white-space:nowrap}#heading h1 a{color:inherit;text-decoration:inherit}#heading h1.long-name{position:relative;top:-5px;display:table-cell;font-size:16px;line-height:20px;word-break:break-all;white-space:normal}#heading>.btn{display:block;float:left;height:20px;padding:1px 5px;margin:0;margin:5px 0 0 10px;font-size:12px;font-weight:lighter;line-height:18px;background-color:rgba(255,255,255,.2);border:none}#heading>.btn:hover{background-color:rgba(0,0,0,.1)}#navbar{margin:0 auto;text-align:center}#navbar .nav{display:inline-block}#navbar .nav>li>a{padding:10px;line-height:30px;color:#fff;border-radius:0;opacity:.9}@media (max-width:1400px){#navbar .nav>li>a{padding:10px 8px}}#navbar .nav>li>a:focus,#navbar .nav>li>a:hover{background:rgba(0,0,0,.15);opacity:1}#navbar .nav>li.active>a{font-weight:700;background:rgba(0,0,0,.1);opacity:1}#navbar .nav>li.divider{display:block;width:2px;height:20px;margin:15px 8px;background:rgba(255,255,255,.4)}@media (max-width:1400px){#navbar .nav>li.divider{margin:15px 5px}}@media (max-width:1300px){#navbar .nav>li.divider{margin:15px 3px}}#navbar .nav>li.divider:last-child{display:none}#navbar .nav .dropdown-menu li>a{text-align:left}#toolbar{position:absolute;top:12px;right:20px;font-size:12px;color:#fff}@media (min-width:1400px){#toolbar{right:40px}}#extraNav{text-align:right}#extraNav>li{display:inline-block;float:none;text-align:left}#extraNav>li>a{display:block;padding:0;color:#fff;opacity:.75}#extraNav>li>a:hover{text-decoration:unset;background-color:rgba(0,0,0,.1);opacity:1}#extraNav>li.open>a{background-color:rgba(0,0,0,.1)}#extraNav>li+li{margin-left:10px}#showSearchGo{color:#fff;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.5)}#searchbox{position:relative;float:left;width:150px}#searchbox .input-group-btn .btn{position:relative;padding:1px 4px;font-size:12px;line-height:20px;color:#fff;background-color:rgba(255,255,255,.15);border-right:none;border-radius:2px}#searchbox .input-group-btn .btn:after{position:absolute;top:3px;right:0;bottom:3px;display:block;width:1px;content:' ';background-color:rgba(255,255,255,.15)}#searchbox .input-group-btn .btn:hover{background-color:rgba(255,255,255,.25)}#searchGo{position:absolute;top:0;right:-1px;z-index:9;min-width:24px;height:24px;padding:2px 3px;font-size:12px;line-height:20px;color:#fff;background-color:#18a6fd;border-radius:2px}#searchGo:hover{color:#fff!important;background-color:#2e7fff}#searchInput{height:24px;padding:2px 30px 2px 5px;color:#fff;text-align:left;background:rgba(255,255,255,.15);border-color:transparent;border-radius:0 12px 12px 0;-webkit-transition:background .2s,border .2s;-o-transition:background .2s,border .2s;transition:background .2s,border .2s}#searchInput:hover{background:rgba(255,255,255,.25)}#searchInput:focus{color:#333;background:#fff}#searchInput::-webkit-input-placeholder{font-size:12px;color:#fff;color:rgba(255,255,255,.5)}#searchInput::-moz-placeholder{font-size:12px;color:#fff;color:rgba(255,255,255,.5)}#searchInput:-ms-input-placeholder{font-size:12px;color:#fff;color:rgba(255,255,255,.5)}#searchInput::placeholder{font-size:12px;color:#fff;color:rgba(255,255,255,.5)}#searchInput:focus::-webkit-input-placeholder{color:#838a9d}#searchInput:focus::-moz-placeholder{color:#838a9d}#searchInput:focus:-ms-input-placeholder{color:#838a9d}#searchInput:focus::placeholder{color:#838a9d}#searchTypeMenu{min-width:220px}#searchTypeMenu>li{float:left;width:50%}#searchTypeMenu>li>a{margin:4px 0}#userNav .avatar{display:inline-block;vertical-align:middle}#userNav>li>a{padding:2px 6px;line-height:20px;color:#fff;opacity:.9}#userNav>li>a .user-name{max-width:100px;overflow:hidden;font-size:15px;text-overflow:unset;white-space:nowrap}#userNav>li>a:hover{background-color:rgba(0,0,0,.1);opacity:1}#userNav>li>a:hover>i{opacity:1}#userNav>li>a span{vertical-align:middle}#userNav>li.open>a{background-color:rgba(0,0,0,.1)}#userNav>li.has-new-items>a{position:relative}#userNav>li.has-new-items>a:before{position:absolute;top:3px;right:-1px;display:block;width:4px;height:4px;content:' ';background-color:#fc5959;border-radius:50%}#userNav .dropdown-menu{min-width:150px}#userNav .dropdown-menu>li>a>.icon{position:absolute;top:10px;right:5px;display:block;width:20px;height:20px;line-height:20px;text-align:center}#userNav .user-profile-item>a{position:relative;padding-left:45px}#userNav .user-profile-item .avatar{position:absolute;top:6px;left:5px}#userNav .user-profile-name{font-size:16px}#userNav .user-profile-role{font-size:12px;color:#a9abb8}#userNav .no-role .user-profile-role{display:none}#userNav .no-role .user-profile-name{line-height:40px}#subHeader{min-height:50px;background:#fff}#subHeader>.container{padding:0 20px}@media (min-width:1400px){#subHeader>.container{padding:0 40px}}#pageNav{position:absolute;top:8px;left:0;left:20px}@media (min-width:1400px){#pageNav{left:40px}}#subNavbar{margin-top:5px;font-size:13px;text-align:center}#subNavbar .nav{display:inline-block}#subNavbar .nav>li>a{padding:8px 12px;line-height:24px;color:#313c52}#subNavbar .nav>li>a:hover{color:#313c52;background-color:rgba(0,0,0,.075)}#subNavbar .nav>li.active>a{font-weight:700;color:#2e7fff}#subNavbar .nav>li.divider{display:block;width:2px;height:20px;margin:9px 5px;background-color:rgba(0,0,0,.05)}#subNavbar .dropdown-menu{text-align:left}[lang=en] #subNavbar>.nav>li>a{padding-right:8px;padding-left:8px}#pageActions{position:absolute;top:9px;right:20px}@media (min-width:1400px){#pageActions{right:40px}}.cell{padding:10px;background-color:#fff;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045);box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045)}.cell+.cell{margin-top:10px}.cell>.panel{margin:0;-webkit-box-shadow:none;box-shadow:none}.cell>.panel>.panel-heading{padding:5px 5px 10px}.cell>.panel>.panel-heading .panel-actions{padding:0}.cell>.panel>.panel-body{padding:5px}.cell>.table{margin:0}#main{min-width:1200px;padding:20px 0}#main>.container{padding:0 20px}@media (min-width:1400px){#main>.container{padding:0 40px}}#header,#header+#main{min-width:1200px}#mainMenu{margin:-10px 0 8px}.main-content{padding:20px;background-color:#fff;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045);box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045)}@media (min-width:1400px){.main-content>.center-block{max-width:1350px;padding:20px;border:1px solid #eee}.main-content>.center-block .main-header{background-color:#f1f1f1}}.main-content>h2{margin:0 0 20px}.main-content .cell{-webkit-box-shadow:none;box-shadow:none}.main-header{padding:5px 20px;border-bottom:1px solid #eee}.main-header:after,.main-header:before{display:table;content:" "}.main-header:after{clear:both}.main-header>h2{display:block;float:left;margin:0 10px 0 0;font-size:14px;line-height:34px}.main-header>h2 .label-id{margin-right:5px}.main-header>h2 small{font-size:14px;font-weight:400}.main-content .main-header{margin:-20px -20px 10px}.main-header .label{top:-1px}.main-row{display:table;width:100%;table-layout:fixed}.main-row>[class*=" col-"],.main-row>[class*="-col"],.main-row>[class^=col-]{display:table-cell;vertical-align:top}@media (max-width:720px){.main-row{display:block}.main-row>*{display:block;width:100%}.main-row .side-col{width:100%;padding:0}.main-row .main-col+.side-col,.main-row .side-col+.main-col{margin-top:10px}}.main-row.hide-side .side-col{display:none}.main-form{margin:0}@media (min-width:720px){.main-content>.center-block .main-form{padding-right:20px}}#main .side-col .tabs{padding:5px}#main .side-col .nav-tabs{margin:0 5px 5px 5px}#main .side-col .nav-tabs>li{margin:0}#main .side-col .nav-tabs>li+li{margin-left:10px}#main .side-col .nav-tabs>li>a{padding:8px 5px}#main .side-col .tab-content .tab-pane table{border:none}.main-actions .btn-toolbar{display:inline-block;padding:4px 15px;color:#fff;pointer-events:auto;background:#717171;background-color:rgba(90,90,90,.85);border-radius:4px}.main-actions .btn-toolbar .divider{margin-right:15px;margin-left:15px;border-color:rgba(255,255,255,.1)}.main-actions .btn-toolbar .btn{height:30px;padding-right:10px;padding-left:10px;margin-right:0;color:#fff;background-color:transparent;border:none}.main-actions .btn-toolbar .btn+.btn{margin-left:10px}.main-actions .btn-toolbar .btn:focus,.main-actions .btn-toolbar .btn:hover{background-color:rgba(255,255,255,.2)}.main-actions .btn-toolbar .btn.btn-icon{min-width:32px;padding-right:0;padding-left:0}.main-actions .btn-toolbar .btn+.btn-group{margin-right:0;margin-left:10px}#mainContent .main-col>.main-actions{padding:30px 0 0 0;text-align:center}#mainContent .main-col>.main-actions>.btn-toolbar{visibility:visible;opacity:1;-webkit-transition:opacity .2s;-o-transition:opacity .2s;transition:opacity .2s}#mainActions{position:fixed;top:0;right:0;bottom:0;left:0;text-align:center;pointer-events:none}#mainActions .btn-toolbar{position:relative;top:-90px}#mainActions .dropdown-menu{text-align:left}#mainActions>.container{height:100%}.main-actions-holder{display:none}.main-actions-fixed .main-actions-holder{display:block}.main-actions-fixed #mainContent .main-col>.main-actions{position:fixed;bottom:10px}.main-actions-fixed.body-modal #mainContent .main-col>.main-actions{bottom:20px}#nextPage,#prevPage{position:absolute;top:50%;left:-10px;width:40px;height:60px;padding:10px 0;margin-top:-30px;line-height:40px;color:#fff;text-align:center;pointer-events:auto;background:#717171;background-color:rgba(90,90,90,.85);-webkit-box-shadow:0 2px 15px 2px rgba(0,0,0,.05);box-shadow:0 2px 15px 2px rgba(0,0,0,.05)}#nextPage:hover,#prevPage:hover{-webkit-box-shadow:0 2px 15px 2px rgba(0,0,0,.15);box-shadow:0 2px 15px 2px rgba(0,0,0,.15)}#nextPage>i,#prevPage>i{display:block;font-size:18px;line-height:36px}#nextPage{right:-10px;left:auto}@media (max-width:1800px){#prevPage{left:-3px}#nextPage{right:-3px}}#sidebarHeader{position:relative;float:left;width:180px;height:34px;padding-right:20px;margin-right:20px;background:#fff;border-left:4px solid #2e7fff;border-radius:4px 2px 2px 4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045);box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045)}#sidebarHeader:after{position:absolute;top:-1px;right:-8px;display:block;width:0;height:0;content:' ';border-color:transparent transparent transparent #fff;border-style:solid;border-width:18px 0 18px 8px}#sidebarHeader .title{padding:0 5px;overflow:hidden;font-size:14px;font-weight:700;line-height:32px;color:#2e7fff;text-align:center;text-overflow:unset;white-space:nowrap}#sidebarHeader .title>a{position:absolute;top:0;right:0;width:20px;opacity:.5}#sidebarHeader .title>a:hover{opacity:1}#sidebar{position:relative;-webkit-transition:width .2s,padding .2s;-o-transition:width .2s,padding .2s;transition:width .2s,padding .2s}#sidebar>.sidebar-toggle{position:absolute;top:0;right:5px;bottom:0;width:10px;cursor:pointer;background:0 0;border-radius:5px;-webkit-transition:background-color .2s,opacity .5s;-o-transition:background-color .2s,opacity .5s;transition:background-color .2s,opacity .5s}#sidebar>.sidebar-toggle>.icon{position:absolute;top:50%;left:-1px;width:12px;height:30px;margin-top:-10px;line-height:30px;color:#fff;text-align:center;background:#7dcdfe;border-radius:6px}#sidebar>.sidebar-toggle>.icon:before{position:relative;left:-1px}#sidebar>.sidebar-toggle:before{position:absolute;top:0;right:-5px;bottom:0;left:-5px;display:block;content:' '}#sidebar>.sidebar-toggle:hover{background:rgba(0,0,0,.075)}#sidebar>.cell{position:relative;left:0;width:180px;-webkit-transition:left .2s,opacity .2s;-o-transition:left .2s,opacity .2s;transition:left .2s,opacity .2s}#sidebar.no-animate>.cell{display:none;-webkit-transition:none;-o-transition:none;transition:none}.hide-sidebar #sidebar>.cell{position:absolute;left:-200px;visibility:hidden;opacity:0}.hide-sidebar #sidebar{position:relative;width:0;padding:0}.hide-sidebar #sidebar>.sidebar-toggle>.icon:before{content:"\e315"}@media (max-width:720px){#sidebar>.cell{width:100%}}#queryBox{max-height:0;padding:0;overflow:hidden;-webkit-transition:cubic-bezier(.175,.885,.32,1) .2s;-o-transition:cubic-bezier(.175,.885,.32,1) .2s;transition:cubic-bezier(.175,.885,.32,1) .2s;-webkit-transition-property:padding,max-height,margin;-o-transition-property:padding,max-height,margin;transition-property:padding,max-height,margin}#queryBox>form{visibility:hidden;-webkit-transition:visibility .2s .2s;-o-transition:visibility .2s .2s;transition:visibility .2s .2s}#queryBox.loading{height:50px}#queryBox.show{min-height:110px;max-height:300px;margin-bottom:10px;overflow:visible}#queryBox.show>form{visibility:visible}#queryBox.divider{border-bottom:1px solid #eee}#main .querybox-toggle.querybox-opened{position:relative;color:#2e7fff;background:0 0;border:none}#main .querybox-toggle.querybox-opened:before{position:absolute;bottom:-14px;left:50%;width:0;height:0;content:' ';border-color:transparent transparent #fff transparent;border-style:solid;border-width:0 10px 10px 10px}#contentNav{padding:5px;background:#fff;border-bottom:1px solid #eee}#contentNav .nav>li>a{position:relative;padding:6px 10px;color:#838a9d}#contentNav .nav>li.active>a{font-weight:700;color:#2e7fff}#contentNav .nav>li.active>a:before{position:absolute;right:10px;bottom:3px;left:10px;display:block;height:2px;content:' ';background:#2e7fff}.body-modal{padding-bottom:0}.body-modal #main,.body-modal .container{min-width:0!important}.body-modal #main{padding:0}.body-modal .main-header{position:fixed;top:0;right:20px;left:20px;z-index:100;padding:13px 48px 13px 0;margin:0;background:#fff}.body-modal #mainContent{padding-top:70px}.body-modal .main-header>h2{max-width:100%;overflow:hidden;font-size:14px;text-overflow:unset;white-space:nowrap}.body-modal .cell,.body-modal .main-content{-webkit-box-shadow:none;box-shadow:none}.body-modal #mainMenu{position:fixed;top:0;right:0;left:0;z-index:100;padding:12px 60px 12px 10px;margin:0;background:#fff}.body-modal #mainMenu>.btn-toolbar.pull-left.divider{display:none}.body-modal #mainMenu>.btn-toolbar{width:100%;margin-left:20px}.body-modal #mainMenu>.btn-toolbar>.divider:first-child{display:none}.body-modal #mainMenu>.btn-toolbar .page-title{width:100%;margin-left:0}.body-modal #mainMenu>.btn-toolbar .page-title>.text{position:relative;top:-2px;display:inline-block;max-width:85%;max-width:-webkit-calc(100% - 100px);max-width:calc(100% - 100px);overflow:hidden;text-overflow:unset;white-space:nowrap;vertical-align:middle}.body-modal #mainMenu+#mainContent.main-row{padding:60px 10px 0}.body-modal #mainMenu+#mainContent.main-row .cell{border:1px solid #efefef;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045);box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045)}.body-modal #mainActions{top:auto}.body-modal #mainActions .btn-toolbar{top:auto;bottom:10px}.body-modal.m-bug-view,.body-modal.m-story-view,.body-modal.m-task-view,.body-modal.m-testcase-view,.body-modal.m-testtask-view,.body-modal.m-todo-view{padding-bottom:20px;border-radius:3px}#tabsNav{position:relative}#tabsNav .tab-pane>.actions{position:absolute;top:-8px;right:0}#tabsNav .tab-pane>.cell,#tabsNav .tab-pane>.main-table{padding:0;border:1px solid #c4c4c4;border-top:none;border-radius:0 0 4px 4px}#tabsNav .tab-pane>.cell .detail-title{padding-left:5px}#helpContent{position:fixed;top:50px;right:0;bottom:40px;left:0;display:none;background-color:#fff}#helpContent .load-error{display:none;padding:20px}#helpContent .show-error .load-error{display:block}#mainMenu>.btn-toobar .btn-link .label-light,#mainMenu>.btn-toolBar .btn-link .label-light,#mainMenu>.btn-toolbar .btn-link .label-light{color:#313c52;background:#fff}#mainMenu>.btn-toobar .btn-active-text,#mainMenu>.btn-toolBar .btn-active-text,#mainMenu>.btn-toolbar .btn-active-text{color:#1e6aeb;background:#e6eaf1}#mainMenu>.btn-toobar .btn-active-text .text,#mainMenu>.btn-toolBar .btn-active-text .text,#mainMenu>.btn-toolbar .btn-active-text .text{top:0}#mainMenu>.btn-toobar .btn-active-text .text:after,#mainMenu>.btn-toolBar .btn-active-text .text:after,#mainMenu>.btn-toolbar .btn-active-text .text:after{border:none}#menu{background:#415a72}#userNav>li>a>.icon{color:#fff;filter:none;opacity:.25;-webkit-filter:none}.picker-option.picker-option-active,.picker-single .picker-option.picker-option-selected.picker-option-active{background:#2e7fff}select.form-control option.selected,select.form-control option:hover{background:#2e7fff}#guideDialog .project-type-img:hover,#guideDialog .project-type.active img,.kanbans .panel:hover{border-color:#2e7fff}#dashboard .block-welcome .progress{background:#edeef2}#dashboard .block-welcome .progress>.progress-bar{background:#3785ff}#assigntomeBlock .nav>li.active>a{color:#1e6aeb}.consumedTotally{color:#2e7fff;background:#e6f0ff}#toggleFoldIcon .icon-border{border:1px solid #d5e5ff}#createEffort .table tbody .date-group .input-group-addon .icon,#recordForm .table tbody .date-group .input-group-addon .icon,#toggleFoldIcon{color:#2e7fff}.text-middle td,.text-middle th{vertical-align:middle}.text-center td,.text-center th{text-align:center}.c-sm{width:40px}.c-id{width:90px}.c-id-sm{width:70px}.c-id-xs{width:55px}.c-date{width:100px}.c-num,.c-pri,.c-type{width:80px;overflow:hidden}.c-begin,.c-end,.c-time{width:65px}.c-hours{width:60px}.c-actions-1{width:50px}.c-actions-2{width:75px}.c-actions-3{width:102px}.c-actions,.c-actions-4{width:128px}.c-actions-5{width:155px}.c-actions-6{width:180px}.c-product,.c-project{width:180px}.c-plan{width:130px}.c-datetime{width:120px}.c-stage,.c-status,.c-user{width:80px}.c-side{width:200px;border-right:10px solid #efefef}.c-assign,.c-assignedTo,.c-openedBy{width:130px}.c-progress{width:155px}.c-assign,.c-assignedTo,.c-openedBy,.c-product,.c-project,.c-status,.c-url,.c-user{overflow:hidden;text-overflow:unset;white-space:nowrap}.assigned-none{color:#1e6aeb!important;background:inherit}.assigned-current{color:#f38f19!important;background:inherit}.assigned-other{color:#313c52!important;background:inherit}a.assigned-current:hover,a.assigned-none:hover,a.assigned-other:hover{color:#1e6aeb!important;background:#fff;border-color:#fff!important}a.assigned-current:hover .icon,a.assigned-none:hover .icon,a.assigned-other:hover .icon{color:#1e6aeb!important}td.c-name,td.c-title{overflow:hidden;text-align:left!important;text-overflow:unset;white-space:nowrap}td.c-actions{position:relative;padding-top:0;padding-bottom:0;overflow:hidden;text-overflow:unset;white-space:nowrap;vertical-align:middle}td.c-actions .btn-link{color:#313c52;background:0 0}td.c-actions .btn-link:hover{color:#2e7fff;background:#e6f0ff}td.c-actions .btn.disabled,td.c-actions .btn[disabled]{pointer-events:auto}td.c-actions .more{position:absolute;top:50%;right:100%;display:none;padding-right:4px;padding-left:20px;margin-top:-15px;margin-right:-6px;white-space:nowrap;background-color:#fafafa;-webkit-transition:opacity .3s,margin .3s;-o-transition:opacity .3s,margin .3s;transition:opacity .3s,margin .3s}tr:hover td.c-actions .more{display:block}td:hover+td.c-actions>.more{margin-right:-15px;pointer-events:none;opacity:.15}tr[data-url]{cursor:pointer}.table tbody>tr>td,.table thead>tr>th{vertical-align:middle}.table tbody>tr>td.has-btn,.table thead>tr>th.has-btn{padding-top:1px;padding-bottom:1px;overflow:visible}.table tbody>tr>td .progress,.table thead>tr>th .progress{height:6px}.table .em,.table em{color:#313c52}.table .divider{border-bottom:10px solid #efefef}.table .divider-top{border-top:10px solid #efefef}.table .btn-icon-left{max-width:100%;padding-left:20px;overflow:hidden;line-height:18px;text-align:left;text-overflow:unset;background:0 0;border-color:#eaf3fc}.table .btn-icon-left>.icon{width:20px;font-size:14px;background:0 0!important;opacity:0}.table .btn-icon-left.btn-sm{height:26px;font-size:13px}.table .btn-icon-left:active,.table .btn-icon-left:focus,.table .btn-icon-left:hover{border-color:rgba(0,0,0,.2)}.table .btn-icon-left:active>.icon,.table .btn-icon-left:focus>.icon,.table .btn-icon-left:hover>.icon{opacity:1}.table .btn-icon-left>.text{padding-left:25px}.table thead>tr>th.c-assign,.table thead>tr>th.c-assignedTo{padding-left:29px}.table a{vertical-align:middle}.table caption{margin-bottom:5px;background:#f1f1f1;border:none}.is-firefox .table .btn-icon-left>.icon{line-height:22px}.table tbody>tr>td.c-name a{color:#2463c7}.table tbody>tr>td.c-name a:hover{color:#1e6aeb}.table.table-data tbody>tr:hover,.table.table-form tbody>tr:hover{background:0 0}.main-table{border-radius:4px}.main-table>.table,.main-table>.table-footer,.main-table>.table-header,.main-table>.table-responsive{-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045);box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045)}.main-table .table{font-size:13px;table-layout:fixed;background-color:#fff;border-radius:4px 4px 0 0}.main-table .table.table-lg{font-size:14px}.main-table .table .btn-icon-left{border-color:transparent}.main-table .table .btn-icon-left>.icon{background:0 0;border-radius:4px}.main-table .table .btn-icon-left.btn-sm{height:26px}.main-table .table .btn-icon-left:hover{border-color:rgba(0,0,0,.2)}.main-table .table .btn-icon-left:hover>.icon{background:#e6f0ff;border-radius:4px 0 0 4px}.main-table tbody>tr>td,.main-table thead>tr>th{min-height:36px;padding:2px 8px;line-height:30px}.main-table tbody>tr>td:first-child,.main-table thead>tr>th:first-child{padding-right:4px;padding-left:15px}.main-table thead>tr>th{overflow:hidden;text-overflow:unset;white-space:nowrap}.main-table td,.main-table th{border-bottom:none!important}.main-table thead>tr{background:#fcfdfe;border-bottom:1px solid #f4f5f7}.main-table tbody>tr:last-child>td{border-bottom:1px solid #ddd}.main-table tbody>tr>td{position:relative}.main-table tbody>tr>td .label{max-width:200px;overflow:hidden;text-overflow:unset;white-space:nowrap}.main-table tbody>tr>td>a{line-height:28px;color:#2463c7}.main-table tbody>tr>td>a:not(.btn):visited{color:#003994;opacity:.9}.main-table tbody>tr>td>a:hover,.main-table tbody>tr>td>a:visited:hover{color:#2e7fff}.main-table tbody>tr>td.c-actions{padding-right:10px}.main-table tbody>tr>td.c-side+td:before,.main-table tbody>tr>td:first-child:before{position:absolute;top:0;bottom:0;left:0;display:block;width:0;content:'';background:#2e7fff;opacity:0;-webkit-transition:.2s linear;-o-transition:.2s linear;transition:.2s linear;-webkit-transition-property:width,opacity,border-radius;-o-transition-property:width,opacity,border-radius;transition-property:width,opacity,border-radius}@-moz-document url-prefix(){.main-table tbody>tr>td.c-side+td:before,.main-table tbody>tr>td:first-child:before{bottom:-1px}}.main-table tbody>tr>td.c-side:before{display:none}.main-table tbody>tr>td.c-name a{color:#2463c7}.main-table tbody>tr>td.c-name a:hover{color:#1e6aeb}.main-table tbody>tr{border-bottom:1px solid #f4f5f7;-webkit-transition:.2s cubic-bezier(.175,.885,.32,1);-o-transition:.2s cubic-bezier(.175,.885,.32,1);transition:.2s cubic-bezier(.175,.885,.32,1);-webkit-transition-property:background-color,-webkit-box-shadow;-o-transition-property:box-shadow,background-color;transition-property:background-color,-webkit-box-shadow;transition-property:box-shadow,background-color;transition-property:box-shadow,background-color,-webkit-box-shadow}.main-table tbody>tr:hover{background:rgba(230,240,255,.8)}.main-table .table-grouped tbody>tr:hover{background:#f0f6ff;-webkit-box-shadow:none;box-shadow:none}.main-table .table-grouped tbody>tr:hover td.c-actions .more{background:#f0f6ff}.main-table tbody>tr.checked{background:#fff4ea}.main-table tbody>tr.checked:hover{background:#ffe6c6}.main-table tbody>tr.checked>td.c-side+td:before,.main-table tbody>tr.checked>td:first-child:before{width:4px;opacity:1}.main-table tbody>tr.checked.row-check-begin{border-top-left-radius:4px;border-top-right-radius:2px}.main-table tbody>tr.checked.row-check-begin>td:first-child:before{border-top-left-radius:4px}.main-table tbody>tr.checked.row-check-end{border-bottom-right-radius:2px;border-bottom-left-radius:4px}.main-table tbody>tr.checked.row-check-end>td:first-child:before{border-bottom-left-radius:4px}.main-table .checkbox-primary{display:inline-block;line-height:20px}.main-table .checkbox-primary label{margin:0}.main-table .table{margin:0}.table-header{padding:4px 0 12px}.table-header .table-statistic{color:#838a9d}.table-header .table-statistic strong{font-size:15px;color:#313c52}.table-header .btn-toolbar{margin-top:-28px}.table-header.fixed-right{position:relative;z-index:5;padding:0}.table-header.fixed-right>.btn-toolbar{position:absolute;top:0;right:1px;z-index:1;padding:1px;margin:0;background:#fff;border-radius:4px}.table-header.fixed-right>.btn-toolbar .btn{opacity:.65}.table-header.fixed-right>.btn-toolbar .btn:hover{opacity:1}.table-header-fixed .table-header{position:fixed;top:0}.table-header-fixed .table-header>.btn-toolbar{background-color:transparent}.table-header-fixed .table-header>.btn-toolbar .btn{color:#fff}.table-footer{position:relative;min-height:40px;padding:6px 15px;background:#fff;border-radius:0 0 4px 4px}.body-modal .table-footer{margin-bottom:20px}.talbe-lg+.table-footer{padding:11px 15px}.table-footer .btn-toolbar,.table-footer .checkbox-primary{float:left}.table-footer .btn-toolbar+.btn-toolbar{margin-left:8px}.table-footer .checkbox-primary{margin:5px 20px 0 0}.table-footer .checkbox-primary.checked label:after{border-color:#22c98d!important}.table-footer .btn{padding:3px 10px;line-height:20px}.table-footer .pager{position:absolute;top:0;right:0;z-index:5;height:40px;padding:6px 5px 6px 10px;margin:0;background:#fff;opacity:1;-webkit-transition:opacity .4s;-o-transition:opacity .4s;transition:opacity .4s}.table-footer .pager:before{position:absolute;top:0;bottom:0;left:-50px;display:block;width:50px;content:' ';background:-webkit-gradient(linear,left top,right top,from(rgba(255,255,255,0)),to(#fff));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0,#fff 100%);background:-o-linear-gradient(left,rgba(255,255,255,0) 0,#fff 100%);background:linear-gradient(to right,rgba(255,255,255,0) 0,#fff 100%)}.table-footer .pager .btn,.table-footer .pager>li>.pager-item,.table-footer .pager>li>.pager-label{color:#838a9d;background:0 0;border-color:transparent}.table-footer .pager .btn,.table-footer .pager>li>a{border-radius:3px}.table-footer .pager .btn:hover,.table-footer .pager>li>a:hover{background:rgba(0,0,0,.1)}.table-footer .pager>li.disabled>a.pager-item{opacity:1}.table-footer .form-control{height:28px;padding:3px 8px}.table-footer .table-statistic{position:relative;z-index:2;float:left;padding-right:30px;line-height:28px;color:#838a9d;background:#fff}.table-footer .table-statistic:hover{z-index:4}.table-footer .table-statistic:hover+.pager{z-index:2;opacity:.3}.table-footer .btn-toolbar+.table-statistic,.table-footer .btn-toolbar+.text{margin-left:10px}.table-footer .text{float:left;line-height:28px}.table-footer.fixed-footer{position:fixed;z-index:10;margin:0;background:rgba(75,75,75,.85);border-top-color:transparent}.table-footer.fixed-footer .checkbox-primary label{color:#fff}.table-footer.fixed-footer .checkbox-primary label:after{border-color:rgba(255,255,255,.8)}.table-footer.fixed-footer .table-statistic{color:#fff;background:0 0}.table-footer.fixed-footer .pager{background:#666}.table-footer.fixed-footer .pager:before{background:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,0)),to(#666));background:-webkit-linear-gradient(left,rgba(0,0,0,0) 0,#666 100%);background:-o-linear-gradient(left,rgba(0,0,0,0) 0,#666 100%);background:linear-gradient(to right,rgba(0,0,0,0) 0,#666 100%)}.table-footer.fixed-footer .pager .btn,.table-footer.fixed-footer .pager>li>.pager-item,.table-footer.fixed-footer .pager>li>.pager-label{color:#fff}.table-footer.fixed-footer .pager .btn:hover,.table-footer.fixed-footer .pager>li>a:hover{background:rgba(255,255,255,.3)}.table-footer.fixed-footer .pager>li.disabled>a.pager-item{opacity:.5}.table-actions{display:none;height:28px}.table-actions.show-always{display:block;pointer-events:none;cursor:not-allowed}.has-row-checked .table-actions{display:block;pointer-events:auto!important;cursor:default}.table-lg tbody>tr>td{padding:9px 10px}.table-lg tbody>tr>td .btn+.btn{margin-left:5px}.table.has-sort-head thead>tr>th{padding-right:0}.table.has-sort-head thead>tr>th>a{position:relative;display:inline-block;padding-right:16px;color:#313c52}.table.has-sort-head thead>tr>th>a:after,.table.has-sort-head thead>tr>th>a:before{position:absolute;top:0;right:0;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;line-height:30px;color:#313c52;text-transform:none;content:"\f0de";opacity:.5;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.table.has-sort-head thead>tr>th>a:after{content:"\f0dd"}.table.has-sort-head thead>tr>th>a.sort-down,.table.has-sort-head thead>tr>th>a.sort-up{color:#000;text-decoration:none}.table.has-sort-head thead>tr>th>a:hover,.table.has-sort-head thead>tr>th>a:hover:after,.table.has-sort-head thead>tr>th>a:hover:before{color:#2e7fff;opacity:1}.table.has-sort-head thead>tr>th>a.sort-down:after,.table.has-sort-head thead>tr>th>a.sort-up:before{color:#000;opacity:1}.head-fixed .datatable-head-span .table,.table.fixed-header-copy{z-index:10;color:#fff;background:rgba(75,75,75,.85)}.head-fixed .datatable-head-span .table thead>tr,.table.fixed-header-copy thead>tr{background:0 0}.head-fixed .datatable-head-span .table thead>tr>th,.table.fixed-header-copy thead>tr>th{color:#eee}.head-fixed .datatable-head-span .table thead>tr>th>a,.table.fixed-header-copy thead>tr>th>a{color:#eee}.head-fixed .datatable-head-span .table thead>tr>th>a:hover,.table.fixed-header-copy thead>tr>th>a:hover{color:#fff}.head-fixed .datatable-head-span .table thead>tr>th>a:after,.head-fixed .datatable-head-span .table thead>tr>th>a:before,.table.fixed-header-copy thead>tr>th>a:after,.table.fixed-header-copy thead>tr>th>a:before{color:#eee}.head-fixed .datatable-head-span .table thead>tr>th>a.sort-down,.head-fixed .datatable-head-span .table thead>tr>th>a.sort-down:after,.head-fixed .datatable-head-span .table thead>tr>th>a.sort-up,.head-fixed .datatable-head-span .table thead>tr>th>a.sort-up:before,.head-fixed .datatable-head-span .table thead>tr>th>a:hover,.head-fixed .datatable-head-span .table thead>tr>th>a:hover:after,.head-fixed .datatable-head-span .table thead>tr>th>a:hover:before,.table.fixed-header-copy thead>tr>th>a.sort-down,.table.fixed-header-copy thead>tr>th>a.sort-down:after,.table.fixed-header-copy thead>tr>th>a.sort-up,.table.fixed-header-copy thead>tr>th>a.sort-up:before,.table.fixed-header-copy thead>tr>th>a:hover,.table.fixed-header-copy thead>tr>th>a:hover:after,.table.fixed-header-copy thead>tr>th>a:hover:before{color:#fff}.head-fixed .datatable-head-span .table thead>tr>th>.dropdown>a,.table.fixed-header-copy thead>tr>th>.dropdown>a{color:#eee}.head-fixed .datatable-head-span .table thead>tr>th>.dropdown>a:hover,.table.fixed-header-copy thead>tr>th>.dropdown>a:hover{color:#fff}.head-fixed .datatable-head-span .table .checkbox-primary,.table.fixed-header-copy .checkbox-primary{z-index:1}.head-fixed .datatable-head-span .table .checkbox-primary label,.table.fixed-header-copy .checkbox-primary label{color:#fff}.head-fixed .datatable-head-span .table .checkbox-primary label:after,.table.fixed-header-copy .checkbox-primary label:after{border-color:rgba(255,255,255,.8)}.head-fixed .datatable-head-span .table .checkbox-primary.checked label:after,.table.fixed-header-copy .checkbox-primary.checked label:after{border-color:#22c98d!important}.table-data{margin:0;table-layout:fixed}.table-data tbody>tr>td,.table-data tbody>tr>th{padding:6px 8px;word-break:break-all;border:none}.table-data tbody>tr>th{width:70px;padding-left:0;font-weight:400;color:#838a9d;text-align:right;vertical-align:middle}.table-data tbody>tr>td{padding-right:0}.table-data tbody>tr>td>a{color:#2463c7}.table-data tbody>tr>td>a:not(.btn):visited{color:#003994}.table-data tbody>tr>td>a:hover,.table-data tbody>tr>td>a:visited:hover{color:#2e7fff}.table-data ol,.table-data ul{margin:0}.fixed-head-table{background:rgba(0,0,0,.7);border-bottom:1px solid #ddd}.fixed-head-table thead>tr>th{color:#fff}.table-empty-tip{padding:80px 10px;text-align:center;background:#fff}.not-firefox .table-grouped>tbody>tr>td.c-side{background:#fff!important}.table-grouped .group-toggle{cursor:pointer}.table-grouped .group-toggle.group-summary{border-top:10px solid #efefef}.table-grouped tbody>tr>td:first-child,.table-grouped thead>tr>th:first-child{padding-left:8px}.group-expand-all,.table-group-collapsed .group-collapse-all{display:none}.table-group-collapsed .group-expand-all{display:inline-block}.table-auto{table-layout:auto}.datatable .table>tbody>tr.checked.hover>td,.datatable .table>tbody>tr.checked>td.col-hover{background:#ffe6c6}body.has-fixed-footer{padding-bottom:60px}.table.with-footer-fixed{margin-bottom:20px}.table-nest-hide{display:none!important}th.table-nest-title{position:relative;padding-left:30px!important}.table-nest-icon{position:relative;display:inline-block;width:22px;height:22px;font-size:16px;color:#a6aab8;text-align:center;border-radius:4px}.table-nest-toggle:before{position:absolute;top:0;right:0;bottom:0;left:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:12px;color:#888fa1;content:"\e926";-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.table-nest-toggle:after{position:absolute;top:4px;right:4px;bottom:4px;left:4px;display:inline-block;content:"";border:1px solid;border-radius:50%}.table-nest-toggle:hover{color:#2e7fff;background-color:rgba(0,0,0,.1)}.table-nest-child-hide .table-nest-toggle:before{font-size:12px;content:"\e925"}th.table-nest-title .table-nest-toggle{position:absolute!important;top:7px;left:8px}.table-nest-toggle.table-nest-toggle-global{width:22px;height:22px;padding:0!important;line-height:22px;text-align:center;border-radius:4px}.table-nest-toggle.table-nest-toggle-global:before{position:absolute!important;font-size:12px!important;line-height:22px!important;content:"\e959"!important;opacity:1!important}.table-nest-toggle.table-nest-toggle-global:after{display:none!important}.table-nest-collapsed .table-nest-toggle.table-nest-toggle-global:before{font-size:12px!important;content:"\e95a"!important}.disable-empty-nest-row .is-nest-child .table-nest-icon:before,.disable-empty-nest-row .no-nest .table-nest-icon:before{position:relative;top:-1px;width:6px;min-width:6px;height:6px;content:' ';background-color:#c4c4c4;border-radius:1px}.table-nest-child-hover>td:first-child,.table-nest-hover>td:first-child{-webkit-box-shadow:inset 3px 0 0 #c4c4c4;box-shadow:inset 3px 0 0 #c4c4c4}.c-manager .avatar{position:relative;z-index:2;line-height:17px;border:1.5px solid transparent}.c-manager .avatar.avatar-top{border-color:#ffaf65}.c-manager a{position:absolute;top:8px;left:16px;height:20px;padding:0 8px 0 13px;line-height:20px!important;color:#313c52!important;background:#f4f5f7;border-radius:10px}.article-content{overflow:auto}.article-content img{margin-top:0}.article-content table{margin:10px 0}.article-content table td,.article-content table th{border:1px solid #cbd0db!important}.article-content table th{background:#eee}.article-content a{color:#2e7fff}.article-content a:focus,.article-content a:hover{color:#18a6fd}.article-content,.article>.content{word-wrap:break-word}.detail{padding:10px 0;margin:0 10px}.detail+.detail{padding-top:25px;border-top:1px solid #eee}.detail-title{font-size:14px;font-weight:700;line-height:20px}.detail-title>.pull-right{position:relative;top:-8px}h2.detail-title{margin:0;font-size:15px;font-weight:700}h2.detail-title .label,h2.detail-title .label-id{position:relative;top:-1px}.detail-content{padding:0;margin-top:10px}.detail-content em{color:#313c52}.detail-content .list-unstyled>li+li{margin-top:5px}.side-col .detail-content{padding-left:0}details.detail{padding:10px 0}details.detail summary{position:relative;cursor:pointer;outline:0}details.detail summary::-webkit-details-marker{display:none}details.detail summary:after{position:absolute;top:0;right:0;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-transform:none;content:"\e316";opacity:.4;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}details.detail[open] summary:after{content:"\e313"}.files-list{padding-left:0;list-style:none}.files-list>li>a{display:block;line-height:24px}.files-list>li>a>.icon{display:inline-block;margin-right:5px;opacity:.7}.files-list>li>a:hover{color:#2e7fff}.files-list>li>.right-icon{opacity:0;-webkit-transition:opacity .2s;-o-transition:opacity .2s;transition:opacity .2s}.files-list>li:hover>.right-icon{opacity:1}.histories-list{padding-left:15px;margin-bottom:0}.histories-list>li{position:relative}.histories-list>li+li{margin-top:5px}.histories-list>li strong{color:#313c52}.histories-list .comment,.histories-list .show-form .comment-edit-form{padding:5px 5px 5px 10px;margin:5px 0 0;background-color:rgba(0,0,0,.025);border:1px solid #eee}.histories-list .btn-edit-comment{position:absolute;top:28px;right:2px}.histories-list .comment-edit-form,.histories-list .show-form .btn-edit-comment,.histories-list .show-form .comment{display:none}.histories-list .show-form .comment-edit-form{display:block;padding:10px;border:1px solid #eee}.histories .btn-mini{width:16px;min-width:16px;height:16px;overflow:hidden;line-height:16px;color:#cbd0db;vertical-align:-8%;border-radius:1px}.histories .btn-mini:focus,.histories .btn-mini:hover{color:#2e7fff;border-color:#2e7fff}.histories .show-changes .btn-expand>.icon:before{content:"\e926"}.histories .btn-strip{display:none}.histories .show-changes .btn-strip{display:inline-block}.history-changes{display:none;padding:5px;margin-bottom:-5px;margin-left:5px;font-size:12px;line-height:20px}.history-changes blockquote{padding:5px 5px 5px 10px;margin:5px 0 0;font-size:12px;background-color:rgba(0,0,0,.05);border-left:3px solid #eee}.history-changes blockquote.original{display:none}.show-changes .history-changes,.show-original .history-changes blockquote.original{display:block}.show-original .history-changes blockquote.textdiff{display:none}.syntaxhighlighter{overflow:auto}.list-group{overflow-y:auto}.list-group>a{display:block;padding:2px 10px 2px 5px;overflow:hidden;line-height:20px;text-overflow:unset;white-space:nowrap;border-radius:4px}.list-group>a+a{margin-top:5px}.list-group>a>.icon{display:inline-block;margin-right:3px;opacity:.5}.list-group>a.selected{color:#e6f0ff;background-color:#2e7fff}.list-group>a.active{color:#2e7fff;background-color:#e6f0ff}.list-group>a.active:hover,.list-group>a:hover{color:#fff;background-color:#2e7fff}.list-group>.heading{padding:2px 5px;line-height:20px;color:#838a9d}.list-group>a+.heading{margin-top:4px}.dropup .search-box-sink{padding-top:5px;padding-bottom:45px}.dropup .search-box-sink .search-box{position:absolute;right:10px;bottom:10px;left:10px;margin:0}.dropup .search-box-sink .search-box+.list-group{height:auto;max-height:171px}.search-list{min-width:200px;max-width:300px;padding:0}.search-list .search-box{float:none;width:auto;margin:10px}.search-list .search-box .icon-search{opacity:.5}.search-list .list-group{max-height:248px;padding:5px 10px;margin:5px 0}.dropup .search-list .search-box+.list-group{height:171px;padding-top:0}.search-list .search-input{height:30px}.search-list .input-control-icon-right{height:28px;line-height:28px}.search-list .list-group>a.active{color:inherit;background-color:inherit}.search-list.searchbox-focus .list-group>a.active{color:#2e7fff;background-color:#e6f0ff}.search-list .list-group>a.active:hover,.search-list.searchbox-focus .list-group>a.active:hover{color:#fff;background-color:#2e7fff}#dropMenu{width:initial;max-width:initial}#dropMenu>.search-box{width:100%;padding:10px 10px 0;margin:0}#dropMenu>.search-box .icon-search{color:#333}#dropMenu>.search-box.has-icon-right>.form-control{padding-left:26px}#dropMenu .input-control-icon-left{top:10px;left:10px}#dropMenu .input-control-icon-right{top:11px;right:11px}#dropMenu .input-control-icon-right .icon{position:relative;top:2px}#dropMenu .list-group{max-height:initial;margin:0}#dropMenu .table-row{margin:0 -10px;table-layout:auto}#dropMenu .table-col{position:relative;width:100%;min-width:250px;max-width:450px}#dropMenu .table-col .list-group{max-height:300px;padding:0 10px 5px}#dropMenu .col-left{padding-bottom:30px}#dropMenu .col-right{display:none}#dropMenu .col-footer{position:absolute;right:0;bottom:-5px;left:0;padding:8px 10px;border-top:1px solid #eee}#dropMenu .col-footer>a{opacity:.8}#dropMenu .col-footer>a:hover{opacity:1}#dropMenu.show-right-col .table-col{width:50%}#dropMenu.show-right-col .col-right{display:table-cell;border-left:1px solid #eee}#dropMenu.show-right-col .col-right>.list-group{max-height:335px;margin:0}#dropMenu.show-right-col .col-right>.list-group>a{opacity:.7}#dropMenu.show-right-col .col-right>.list-group>a:hover{opacity:1}#dropMenu.show-right-col .toggle-right-col>.icon-angle-right:before{content:"\e314"}#dropMenu.has-search-text .list-group{overflow-x:hidden}#dropMenu.has-search-text>.search-box{width:100%!important}#dropMenu.has-search-text>.list-group>.table-row{display:block}#dropMenu.has-search-text>.list-group>.table-row>.table-col{display:block;width:100%}#dropMenu.has-search-text .col-left{padding-bottom:0}#dropMenu.has-search-text .pull-right.toggle-right-col{display:none}#dropMenu.has-search-text .col-left .list-group{margin-bottom:0}#dropMenu.has-search-text .col-right .list-group{margin-bottom:0}#dropMenu.has-search-text .col-right .list-group>a{opacity:.7}#dropMenu.has-search-text .col-footer,#dropMenu.has-search-text .hide-in-search{display:none}#swapper{position:relative}#swapper #dropMenu .tree li>.selected{color:#2e7fff!important;background:#e6f0ff!important}#swapper #dropMenu .list-group>a.active:hover,#swapper #dropMenu .search-box .list-group>a.active,#swapper #dropMenu .tree li>.clickable:hover,#swapper #dropMenu.searchbox-focus .list-group>a.active:hover{color:#fff!important;background:#2e7fff!important}#swapper #dropMenu .tree{margin:0}#pageNav #dropMenu .list-group>a.active{color:inherit;background-color:inherit}#pageNav #dropMenu .list-group>a.active:hover,#pageNav #dropMenu.searchbox-focus .list-group>a.active:hover{color:#2e7fff!important;background:#e6f0ff!important}#pageNav #dropMenu .list-group>a.active.selected,#pageNav #dropMenu .list-group>a.active.selected:hover{color:#fff!important;background:#2e7fff!important}.release-path{overflow:hidden}.release-line{display:table;width:100%;padding:0;table-layout:fixed}.release-line>li{display:table-cell;list-style:none}.release-line>li>a{position:relative;display:block}.release-line>li>a:before{position:absolute;left:0;display:block;width:13px;height:13px;content:' ';background:#fff;border:2px solid #838a9d;border-radius:50%}.release-line>li>a:after{position:absolute;left:5px;display:block;width:2px;height:30px;content:' ';background:#c2dbff}.release-line>li>a>.icon{position:absolute;left:4px;font-size:24px}.release-line>li>a .title{display:block;font-size:14px;white-space:nowrap}.release-line>li>a .date,.release-line>li>a .info{display:block;max-height:18px;overflow:hidden;font-size:12px;color:#838a9d;text-overflow:unset;white-space:nowrap}.release-line>li>a:hover:before{background-color:#e6f0ff}.release-line>li>a:hover:after{background-color:#838a9d}.release-line>li>a:hover .title{color:#2e7fff}.release-line>li>a:hover .date,.release-line>li>a:hover .info{color:#838a9d}.release-line>li:nth-child(odd){padding-top:80px;vertical-align:top}.release-line>li:nth-child(odd)>a{height:85px;padding-top:36px;border-top:5px solid #c2dbff}.release-line>li:nth-child(odd)>a:before{top:-9px}.release-line>li:nth-child(odd)>a:after{top:6px}.release-line>li:nth-child(odd)>a>.icon{top:-26px}.release-line>li:nth-child(even){padding-bottom:80px;vertical-align:bottom}.release-line>li:nth-child(even)>a{height:85px;padding-bottom:36px;border-bottom:5px solid #c2dbff}.release-line>li:nth-child(even)>a:before{bottom:-9px}.release-line>li:nth-child(even)>a:after{bottom:6px}.release-line>li:nth-child(even)>a>.icon{bottom:-2px}.release-line>li:last-child>a{border-color:transparent}.release-line>li.active>a:before{border-color:#2e7fff}.release-line>li+li>a>.date,.release-line>li+li>a>.info,.release-line>li+li>a>.title{position:relative;left:-36%}#footer{position:fixed;right:0;bottom:0;left:0;z-index:1010;height:40px;background:#fff;border-top:1px solid #eff1f7}#footer .breadcrumb{padding:10px 0;margin:0}#footer .breadcrumb>li{max-width:200px;overflow:hidden;text-overflow:unset;white-space:nowrap}#footer .breadcrumb>.active,#footer .breadcrumb>li>a{color:#838a9e}#footer .breadcrumb>.active>.icon,#footer .breadcrumb>li>a>.icon{display:none}#footer .breadcrumb>.active:hover,#footer .breadcrumb>li>a:hover{color:#18a6fd}#footer .breadcrumb>li+li:before{content:'>'}#footer>.container{padding:0 20px}@media (min-width:1400px){#footer>.container{padding:0 40px}}#poweredBy{position:absolute;top:4px;right:0;padding:5px 10px}#poweredBy .icon-zentao{color:#0097fd}#poweredBy a{color:#313c52}#poweredBy a:hover{color:#2e7fff}#poweredBy a:hover .icon-zentao{color:#2e7fff}#poweredBy a.text-important{color:#ff6f42}#poweredBy a.text-important:hover{color:#fc5959}#poweredBy a.text-primary{color:#2e7fff}#poweredBy a.text-primary:hover{color:#18a6fd}#poweredBy #aiux{color:#cbd0dc}#noticeBox .alert{-webkit-box-shadow:rgba(0,0,0,.15) 0 3px 10px,rgba(0,0,0,.25) 0 3px 10px;box-shadow:rgba(0,0,0,.15) 0 3px 10px,rgba(0,0,0,.25) 0 3px 10px}#heading{top:0}.header-btn{position:relative;padding:8px 0}.header-btn .btn{position:relative;height:34px;padding:1px 6px;margin:0;overflow:visible;font-size:13px;font-weight:400;line-height:28px;color:#fff;background-color:transparent;border-color:transparent!important;border-right:none;-webkit-transition:none;-o-transition:none;transition:none}.header-btn .btn>.caret{margin-left:0;border-width:4px}.header-btn .btn>.text{display:inline-block;max-width:150px;overflow:hidden;text-overflow:unset;vertical-align:middle}.header-btn .btn:hover{-webkit-box-shadow:none;box-shadow:none}.header-btn .btn:hover,.header-btn.active .btn{color:#fff;background:rgba(0,0,0,.15)}.header-btn .btn:hover>.caret,.header-btn.active .btn>.caret{opacity:1}.header-btn+.header-btn{margin-left:10px}.header-btn+.header-btn:before{position:absolute;top:16px;left:-13px;display:block;font-family:ZentaoIcon;font-size:14px;font-size:16px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-transform:none;content:"\e315";opacity:.6;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.header-btn .dropdown-menu{margin-top:-10px}#toolbar{top:0;height:50px}#userNav>li{margin-right:0}#userNav>li>a{padding:10px 5px}#userNav>li>a>.icon{font-size:30px;filter:brightness(1.2) hue-rotate(30deg);opacity:.9;-webkit-filter:brightness(1.2) hue-rotate(30deg)}#userNav>li:hover>a{background-color:rgba(0,0,0,.1)}#userNav .dropdown-menu>li>a{position:relative;padding-left:24px}#userNav .dropdown-menu>li>a>.icon{top:1px;left:0}#userNav .dropdown-menu>li.user-profile-item>a{padding-left:45px}
\ No newline at end of file
+*/.chosen-container{position:relative;display:block;font-size:13px;vertical-align:middle;zoom:1;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.chosen-container .chosen-drop{position:absolute;top:100%;z-index:1010;display:none;width:100%;background:#fff;border:1px solid #b2b2b2;border:1px solid rgba(0,0,0,.15);border-top:0;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.chosen-container .chosen-drop.chosen-drop-size-limited{border-top:1px solid rgba(0,0,0,.15)}.chosen-container .chosen-drop.chosen-auto-max-width{min-width:100%;border-top:1px solid rgba(0,0,0,.15);opacity:0}.chosen-container .chosen-drop.chosen-auto-max-width>.chosen-results>li{display:inline-block;white-space:nowrap}.chosen-container .chosen-drop.chosen-auto-max-width.in{opacity:1}.chosen-container .chosen-drop.chosen-auto-max-width.in>.chosen-results>li{display:block;white-space:normal}.chosen-container .chosen-drop.chosen-no-wrap>.chosen-results>li{overflow:hidden;text-overflow:ellipsis;white-space:nowrap!important}.chosen-container.chosen-with-drop .chosen-drop{display:block}.chosen-container a{cursor:pointer}.chosen-container.chosen-up .chosen-drop{top:inherit;bottom:100%;margin-top:auto;margin-bottom:-1px;border-radius:2px 2px 0 0;-webkit-box-shadow:0 -3px 5px rgba(0,0,0,.175);box-shadow:0 -3px 5px rgba(0,0,0,.175)}.chosen-container.chosen-highlight-selected .result-selected{color:#2e7fff;background:#e6f0ff}.chosen-container-single .chosen-single{display:block;width:100%;height:32px;padding:5px 8px;overflow:hidden;line-height:1.42857143;color:#0b0f18;text-decoration:none;white-space:nowrap;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #dcdcdc;border-radius:2px;-webkit-box-shadow:none;box-shadow:none;-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s,-webkit-box-shadow ease-in-out .15s}.chosen-container-single .chosen-default{color:#838a9d}.chosen-container-single .chosen-single>span{display:block;margin-right:26px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chosen-container-single .chosen-single-with-deselect span{margin-right:38px}.chosen-container-single .chosen-single abbr{position:absolute;top:5px;right:24px;display:block;width:20px;height:20px;font-family:sans-serif;font-size:18px;font-weight:700;line-height:18px;color:#000;text-align:center;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.chosen-container-single .chosen-single abbr:before{display:block;content:'×'}.chosen-container-single .chosen-single abbr:focus,.chosen-container-single .chosen-single abbr:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}.chosen-container-single .chosen-single div{position:absolute;top:0;right:0;display:block;height:100%;padding:5px 8px}.chosen-container-single .chosen-single div b{display:inline-block;width:0;height:0;margin-bottom:2px;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent;opacity:.5}.chosen-container-single .chosen-search{position:relative;z-index:1010;padding:3px 4px;margin:0;white-space:nowrap}.chosen-container-single .chosen-search input[type=text]{width:100%;height:27px;padding:2px 26px 2px 8px;margin:1px 0;font-size:12px;line-height:1.5;background-color:#fff;border:1px solid #dcdcdc;border-radius:2px;outline:0}.chosen-container-single .chosen-search input[type=text]:focus{border-color:#2e7fff}.chosen-container-single .chosen-search:before{position:absolute;top:10px;right:10px;display:block;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;color:#838a9d;text-transform:none;content:"\e928";speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.chosen-container-single .chosen-drop{margin-top:-1px;-webkit-background-clip:padding-box;background-clip:padding-box;border-radius:0 0 4px 4px}.chosen-container-single.chosen-container-single-nosearch .chosen-search{position:absolute;left:-9999px}.chosen-container .chosen-results{position:relative;max-height:240px;padding:0;margin:0;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch}.chosen-container .chosen-results li{display:none;padding:5px 10px;margin:0;line-height:15px;list-style:none;-webkit-transition:background-color .2s cubic-bezier(.175,.885,.32,1);-o-transition:background-color .2s cubic-bezier(.175,.885,.32,1);transition:background-color .2s cubic-bezier(.175,.885,.32,1);-webkit-touch-callout:none}.chosen-container .chosen-results li.active-result{display:list-item;cursor:pointer}.chosen-container .chosen-results li.disabled-result{display:list-item;color:#ccc;cursor:default}.chosen-container .chosen-results li.highlighted{color:#fff;background-color:#2e7fff}.chosen-container .chosen-results li.no-results{display:list-item;background:#f4f4f4}.chosen-container .chosen-results li.group-result{display:list-item;font-weight:700;cursor:default}.chosen-container .chosen-results li.group-option{padding-left:15px}.chosen-container .chosen-results li em{font-style:normal;text-decoration:underline}.chosen-container-multi .chosen-choices{position:relative;width:100%;min-height:32px;min-height:30px\9;padding:0;margin:0;overflow:hidden;cursor:text;background-color:#fff;border:1px solid #dcdcdc;border-radius:2px;-webkit-box-shadow:none;box-shadow:none;-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s,-webkit-box-shadow ease-in-out .15s}.chosen-container-multi .chosen-choices:after,.chosen-container-multi .chosen-choices:before{display:table;content:" "}.chosen-container-multi .chosen-choices:after{clear:both}.chosen-container-multi .chosen-choices li{display:block;float:left;padding:0 6px;margin:5px 4px;list-style:none}.chosen-container-multi .chosen-choices li.search-field{padding:0;line-height:12px;white-space:nowrap}.chosen-container-multi .chosen-choices li.search-field input[type=text]{height:20px;font-size:100%;color:#838a9d;background:0 0!important;border:0!important;border-radius:0;outline:0;-webkit-box-shadow:none;box-shadow:none}.chosen-container-multi .chosen-choices li.search-field .default{color:#999}.chosen-container-multi .chosen-choices li.search-field:before{position:absolute;right:8px;bottom:8px;display:block;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;color:#838a9d;text-transform:none;content:"\e928";opacity:0;-webkit-transition:opacity .2s cubic-bezier(.175,.885,.32,1);-o-transition:opacity .2s cubic-bezier(.175,.885,.32,1);transition:opacity .2s cubic-bezier(.175,.885,.32,1);speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.chosen-container-multi .chosen-choices li.search-choice{position:relative;padding:3px 20px 3px 5px;line-height:12px;cursor:default;background-color:#f1f1f1;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #c4c4c4;border-radius:3px;-webkit-box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);-webkit-transition:all .4s cubic-bezier(.175,.885,.32,1);-o-transition:all .4s cubic-bezier(.175,.885,.32,1);transition:all .4s cubic-bezier(.175,.885,.32,1)}.chosen-container-multi .chosen-choices li.search-choice:hover{background-color:#fff;border-color:#ababab;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.1);box-shadow:0 1px 0 rgba(0,0,0,.1)}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close{position:absolute;top:1px;right:0;display:block;width:20px;height:18px;line-height:18px;color:#000;text-align:center;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:before{font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-shadow:0 1px 0 #fff;text-transform:none;content:'\d7';speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:focus,.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}.chosen-container-multi .chosen-choices li.search-choice-disabled{padding-right:5px;color:#666;background-color:#e4e4e4;border:1px solid #ccc}.chosen-container-multi .chosen-choices li.search-choice-focus{background:#d4d4d4}.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close{background-position:-42px -10px}.chosen-container-multi .chosen-results{padding:5px 0;margin:0}.chosen-container-multi .chosen-drop .result-selected{display:list-item;color:#ccc;cursor:default}.chosen-container-active .chosen-single{border-color:#2e7fff;-webkit-box-shadow:none,0 0 8px rgba(46,127,255,.6);box-shadow:none,0 0 8px rgba(46,127,255,.6)}.chosen-container-active.chosen-with-drop .chosen-single{border:1px solid #b2b2b2;border:1px solid rgba(0,0,0,.15);border-bottom-right-radius:0!important;border-bottom-left-radius:0!important;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.chosen-container-active.chosen-with-drop .chosen-single div{background:0 0;border-left:none}.chosen-container-active.chosen-with-drop .chosen-single div b{content:"";border-top:0 dotted;border-bottom:4px solid}.chosen-container-active.chosen-with-drop.chosen-up .chosen-single{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:2px;border-bottom-left-radius:2px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.chosen-container-active .chosen-choices{border-color:#2e7fff;-webkit-box-shadow:none,0 0 8px rgba(46,127,255,.6);box-shadow:none,0 0 8px rgba(46,127,255,.6)}.chosen-container-active .chosen-choices li.search-field input[type=text]{color:#111!important}.chosen-container-active .chosen-choices li.search-field:before{opacity:1}.chosen-disabled{cursor:default;opacity:.5!important}.chosen-disabled .chosen-single{cursor:default}.chosen-disabled .chosen-choices .search-choice .search-choice-close{cursor:default}.chosen-compact.chosen-container-single .chosen-single>.chosen-search{left:0;display:none;padding:3px 4px;opacity:0}.chosen-compact.chosen-container-single .chosen-single>.chosen-search>input{height:25px;padding:2px 26px 2px 4px;font-size:inherit}.chosen-compact.chosen-container-single .chosen-single>.chosen-search:before{top:9px}.chosen-compact.chosen-with-search.chosen-with-drop .chosen-single>.chosen-search{display:block;opacity:1}select.chosen[multiple]{height:32px;overflow:hidden}select.chosen[multiple] option{visibility:hidden}.picker{min-height:32px;background-color:#eee}.picker:not(.picker-ready)>*{display:none}.picker-selections{position:relative;min-height:32px;color:#0b0f18;background-color:#fff;border:1px solid #dcdcdc;border-radius:2px;-webkit-box-shadow:none;box-shadow:none}.picker-focus .picker-selections{border-color:#2e7fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(46,127,255,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(46,127,255,.6)}.disabled .picker-selections{cursor:not-allowed;background-color:#f5f5f5}.picker-multi .picker-selections{padding:3px}.picker-multi.picker-focus .picker-selections:before{position:absolute;right:0;bottom:8px;display:block;width:24px;height:14px;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-align:center;text-transform:none;content:"\e928";opacity:.4;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.picker-multi.picker-focus.picker-loading .picker-selections::before{display:inline-block;content:"\e982";-webkit-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}.picker-multi .picker-selection{position:relative;display:inline-block;padding:0 20px 0 5px;margin:2px;line-height:18px;background-color:#f1f1f1;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #c4c4c4;border-radius:3px;-webkit-box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05)}.picker-selection-single{max-width:100%;padding:5px 20px 5px 8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.picker-focus .picker-selection-single .picker-selection-text{display:none}.picker-selection-single:after{position:absolute;top:7px;right:0;display:block;width:24px;height:14px;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-align:center;text-transform:none;content:"\f0d7";opacity:.4;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.picker-focus .picker-selection-single::after{content:"\e928"}.picker-focus.picker-loading .picker-selection-single::after{display:inline-block;content:"\e982";-webkit-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}.picker-selection-remove{position:absolute;top:0;right:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:18px;height:18px;text-align:center;cursor:pointer;opacity:.2;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.picker-selection-remove:before{font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-shadow:0 1px 0 #fff;text-transform:none;content:'\d7';speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.picker-selection-remove:hover{opacity:.5}.picker-single .picker-selection-remove{top:5px;right:20px;z-index:1}.picker-no-value .picker-selection-remove,.picker-single.picker-focus .picker-selection-remove{display:none}.picker-multi.picker-sortable .picker-selection-text{cursor:move}.picker-search{color:transparent;background:0 0;border:none}.picker-search:focus{color:inherit;outline:0}.disabled .picker-search{cursor:not-allowed}.picker-single .picker-search{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;padding:5px 8px}.picker-multi .picker-search{display:inline-block;width:10px;min-width:10px;max-width:100%;padding:0 2px;margin:2px 0}.picker-placeholder{position:absolute;top:5px;right:20px;left:8px;z-index:0;display:none;overflow:hidden;color:#838a9d;text-overflow:ellipsis;white-space:nowrap}.picker-no-value.picker-focus .picker-placeholder{display:none}.picker-input-empty.picker-focus.picker-no-value .picker-placeholder,.picker-no-value .picker-placeholder{display:block}.picker-drop-menu{position:fixed;z-index:1200;display:none;min-width:100px;background-color:#fff;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.12),0 1px 3px rgba(0,0,0,.1);box-shadow:0 6px 12px rgba(0,0,0,.12),0 1px 3px rgba(0,0,0,.1);opacity:0}.picker-drop-menu.picker-drop-show{display:block}.picker-option-list{padding:5px 0;margin:0;overflow-y:auto}.picker-no-options .picker-option-list{display:none}.picker-option{display:block;min-height:30px;padding:5px 10px;overflow:hidden;clear:both;font-weight:400;line-height:1.42857143;color:#313c52;text-overflow:ellipsis;white-space:nowrap;-webkit-transition:none;-o-transition:none;transition:none}.picker-single .picker-option.picker-option-selected{color:#2e7fff;background:#e6f0ff}.picker-option:focus,.picker-option:hover{text-decoration:none}.picker-option.picker-option-active,.picker-single .picker-option.picker-option-selected.picker-option-active{color:#fff;background-color:#2e7fff}.picker-multi .picker-option.picker-option-selected,.picker-multi .picker-option.picker-option-selected.picker-option-active{color:#ccc;cursor:not-allowed;background-color:#fff}.picker-option.disabled{cursor:not-allowed!important}.picker-option-text-matched{position:relative;color:#2e7fff}.picker-option-text-matched:before{position:absolute;right:0;bottom:-4px;left:0;display:block;height:2px;content:' ';background-color:#2e7fff;border-radius:2px;opacity:.5}.picker-option-active .picker-option-text-matched,.picker-option:hover .picker-option-text-matched{color:#fff}.picker-option-active .picker-option-text-matched:before,.picker-option:hover .picker-option-text-matched:before{background-color:#fff}.picker-message{display:none;height:22px;padding:2px 5px;overflow:hidden;font-size:12px;color:#838a9d;text-overflow:ellipsis;white-space:nowrap;background-color:#f1f1f1}.picker-has-message .picker-message{display:block}.picker-message[data-type=danger]{color:#fc5959}.picker-actions{padding:5px;border-top:1px solid #eee}.picker-no-actions .picker-actions{display:none}.picker-actions .btn{padding:2px 4px}.picker-actions .btn[disabled]{background-color:transparent;filter:grayscale(1);-webkit-filter:grayscale(1)}.picker-actions .btn+.btn{margin-left:4px}.picker-checkable .picker-option{position:relative;padding-left:32px;color:inherit!important;cursor:pointer!important}.picker-checkable .picker-option>.checkbox-primary{position:absolute;top:4px;left:10px}.picker-checkable .picker-option.picker-option-active{background-color:rgba(0,0,0,.05)!important}.picker-checkable .picker-option:hover{background-color:rgba(0,0,0,.075)!important}.picker-checkable .picker-option.picker-option-selected{background-color:#e6f0ff!important}.picker-checkable .picker-option-active .picker-option-text-matched,.picker-checkable .picker-option:hover .picker-option-text-matched{color:#2e7fff!important}.picker-checkable .picker-option-active .picker-option-text-matched:before,.picker-checkable .picker-option:hover .picker-option-text-matched:before{background:#2e7fff!important}:root{--color-primary:#2e7fff;--color-secondary:#18a6fd;--color-pale:#e6f0ff;--color-fore:#313c52;--color-back:#fff;--color-red:#fc5959;--color-yellow:#ff9f46;--color-green:#22c98d;--color-blue:#37b2fe;--color-purple:#8166ee;--color-brown:#ff6f42;--color-danger:#fc5959;--color-warning:#ff9f46;--color-success:#22c98d;--color-info:#37b2fe;--color-special:#8166ee;--color-important:#ff6f42;--color-green-pale:#ecfff8;--color-yellow-pale:#fff4ea;--color-red-pale:#ffebeb;--color-blue-pale:#e7f6ff;--color-brown-pale:#fff2ed;--color-purple-pale:#efecfa;--link-color:#313c52;--link-hover-color:#2e7fff;--color-gray-darker:#0b0f18;--color-gray-dark:#313c52;--color-gray:#838a9d;--color-gray-light:#c4c4c4;--color-gray-lighter:#eee;--color-gray-pale:#f1f1f1;--border-radius:4px;--border-radius-lg:6px;--border-radius-sm:3px;--bg-gradient:#3785ff}.container,.container-fixed,.container-fluid{position:relative}.container{max-width:1800px!important}body{color:#313c52;background-color:#f4f5f7}body.article-content,body.body-modal{background:0 0}body.body-modal{padding:0}@media screen and (min-width:1920px){body{font-size:14px}}a:active,a:focus,button:active,button:focus{outline:0!important}.strong{font-weight:700}.font-normal{font-weight:400!important}.text-middle{vertical-align:middle!important}.text-bottom{vertical-align:bottom!important}.text-top{vertical-align:top!important}.inline-block{display:inline-block!important}.layer{border-radius:4px;-webkit-box-shadow:0 0 20px 0 #bdc9d8;box-shadow:0 0 20px 0 #bdc9d8}.space{margin-bottom:20px}.space-lg{margin-bottom:30px}.space-sm{margin-bottom:10px}.muted{opacity:.5}.text-muted em{color:#313c52}.no-animate{-webkit-transition:none!important;-o-transition:none!important;transition:none!important}.template{display:none!important}.text-left{text-align:left!important}.text-yellow.icon-folder{color:#ff9f46}.table-row{display:table;width:100%;table-layout:fixed}.table-col,.table-row>.col,.table-row>[class*=col-],.table-row>[class*="-col"]{display:table-cell;float:none;vertical-align:top}.side-col{width:200px;padding-right:20px}.side-col.col-4{width:33.3333333%}.col-lg{width:260px}.col-xl{width:320px}.col-sm{width:150px}.col-xs{width:100px}.main-col+.side-col{padding-right:0;padding-left:20px}.row-grid>[class*=col-],.row-grid>[class*="-col"]{padding-top:6px;padding-bottom:6px}hr.space{margin:10px 0;border:none}hr.space-sm{margin:5px 0;border:none}.text-secondary{color:#18a6fd}a.text-primary{color:#2e7fff}.text-primary-400{color:#3785ff}.text-primary-600{color:#1e6aeb}.text-primary-700{color:#2463c7}.text-primary-900{color:#063072}.text-green-400{color:#48d1a0}.text-yellow-400{color:#ffaf65}.text-blue-400{color:#60c2ff}.text-red-400{color:#ff7c7c}.text-important-400{color:#ff9777}.text-purple-400{color:#8a6ff5}.text-green-600{color:#0dbb7d}.text-yellow-600{color:#f38f19}.text-blue-600{color:#00c0e9}.text-red-600{color:#fb2b2b}.text-important-600{color:#ff6f42}.text-purple-600{color:#7055df}.text-green-700{color:#0ca76f}.text-yellow-700{color:#ed8307}.text-blue-700{color:#1099ed}.text-red-700{color:#d91b1b}.text-important-700{color:#ff4912}.text-purple-700{color:#513baa}.text-green-900{color:#186c4e}.text-yellow-900{color:#af5f01}.text-blue-900{color:#046bab}.text-red-900{color:#a20606}.text-important-900{color:#611700}.text-purple-900{color:#30216f}.text-gray-14,.text-title{color:#0b0f18}.icon-important,.text-color,.text-gray-13{color:#313c52}.text-gray-12{color:#5e626d}.icon-common,.text-gray-11{color:#838a9d}.icon-secondary,.text-gray-10{color:#9ea3b0}.text-disabled{color:#c4c4c4}.nav-primary>li>a{min-width:100px;padding:5px 8px;color:#838a9d;border-color:#e7f1fc}.nav-primary>li.active>a{color:#2e7fff;background-color:#e7f1fc;border-color:#e7f1fc}.nav-primary>li.active>a:hover{color:#2e7fff;background-color:#c3dcf7;border-color:#c3dcf7}.end-marker{margin-bottom:20px;color:#c4c4c4;text-align:center}@-webkit-keyframes highlight{0%{background:#fff;outline:1px solid transparent}100%{background:#fff0d5;outline:2px solid #ffdcbc}}@-o-keyframes highlight{0%{background:#fff;outline:1px solid transparent}100%{background:#fff0d5;outline:2px solid #ffdcbc}}@keyframes highlight{0%{background:#fff;outline:1px solid transparent}100%{background:#fff0d5;outline:2px solid #ffdcbc}}.highlight{-webkit-animation:highlight .5s linear 0s 2 alternate;-o-animation:highlight .5s linear 0s 2 alternate;animation:highlight .5s linear 0s 2 alternate}.progress.inline-block{width:100px;margin:0}.w-p5{width:5%!important}.w-p10{width:10%!important}.w-p15{width:15%!important}.w-p20{width:20%!important}.w-p25{width:25%!important}.w-p30{width:30%!important}.w-p35{width:35%!important}.w-p40{width:40%!important}.w-p45{width:45%!important}.w-p50{width:50%!important}.w-p55{width:55%!important}.w-p60{width:60%!important}.w-p65{width:65%!important}.w-p70{width:70%!important}.w-p75{width:75%!important}.w-p80{width:80%!important}.w-p85{width:85%!important}.w-p90{width:90%!important}.w-p94{width:94%!important}.w-p95{width:95%!important}.w-p98{width:98%!important}.w-p99{width:99%!important}.w-p100{width:100%!important}.w-auto{width:auto!important}.w-10px{width:10px!important}.w-20px{width:20px!important}.w-30px{width:30px!important}.w-35px{width:35px!important}.w-40px{width:40px!important}.w-45px{width:45px!important}.w-50px{width:50px!important}.w-60px{width:60px!important}.w-70px{width:70px!important}.w-80px{width:80px!important}.w-90px{width:90px!important}.w-100px{width:100px!important}.w-110px{width:110px!important}.w-120px{width:120px!important}.w-130px{width:130px!important}.w-140px{width:140px!important}.w-150px{width:150px!important}.w-160px{width:160px!important}.w-180px{width:180px!important}.w-200px{width:200px!important}.w-230px{width:230px!important}.w-250px{width:250px!important}.w-300px{width:300px!important}.w-400px{width:400px!important}.w-500px{width:500px!important}.w-600px{width:600px!important}.w-700px{width:700px!important}.w-800px{width:800px!important}.w-900px{width:900px!important}.mw-200px{max-width:200px!important}.mw-300px{max-width:300px!important}.mw-400px{max-width:400px!important}.mw-500px{max-width:500px!important}.mw-600px{max-width:600px!important}.mw-700px{max-width:700px!important}.mw-800px{max-width:800px!important}.mw-900px{max-width:900px!important}.mw-1400px{max-width:1400px!important}.w-id{width:70px!important}.w-pri{width:40px!important}.w-severity{width:50px!important}.w-hour{width:57px!important}.w-date{width:90px!important}.w-status{width:60px!important}.w-resolution,.w-type,.w-user{width:80px!important}.w-p15-f{width:15%!important;min-width:120px!important}.w-p25-f{width:25%!important;min-width:200px!important}.w-p35-f{width:35%!important;min-width:300px!important}.w-p45-f{width:45%!important;min-width:400px!important}.h-5px{height:5px!important}.h-10px{height:10px!important}.h-20px{height:20px!important}.h-30px{height:30px!important}.h-35px{height:35px!important}.h-40px{height:40px!important}.h-45px{height:45px!important}.h-50px{height:50px!important}.h-60px{height:60px!important}.h-70px{height:70px!important}.h-80px{height:80px!important}.h-100px{height:100px!important}.h-120px{height:120px!important}.h-130px{height:130px!important}.h-140px{height:140px!important}.h-150px{height:150px!important}.h-200px{height:200px!important}.pd-0{padding:0!important}.mg-0{margin:0!important}.mgb-20{margin-bottom:20px!important}.mgb-10{margin-bottom:10px!important}.pdb-20{padding-bottom:20px!important}.pdt-20{padding-top:20px!important}.br-0{border-radius:0!important}.bd-0,.bd-none,.borderless{border:none!important}.bg-none{background:0 0!important}.red{color:#fc5959!important}.icon-pro-version{font-size:14px!important}.icon-pro-version:before{position:relative;top:-1px;font-size:14px;color:#fc5959;content:"\e92b"}.bg-primary{color:#fff;background:#3785ff;border-top-color:#2e7fff}.bg-primary-pure{color:#fff;background-color:#2e7fff}.bg-primary-pale{color:#2e7fff;background-color:#e6f0ff}.bg-secondary{color:#fff;background:#18a6fd}.line-gray-8{background:#d8dbde}.line-gray-5{background:#e3e4e9}.line-gray-7{background:#eee}.bg-gray-1{background:#e3e4e9}.bg-gray-2{background:#edeef2}.bg-gray-3{background:#f8f8f8}.bg-green-400{background:#48d1a0}.bg-yellow-400{background:#ffaf65}.bg-blue-400{background:#60c2ff}.bg-red-400{background:#ff7c7c}.bg-important-400{background:#ff9777}.bg-purple-400{background:#8a6ff5}.bg-green-600{background:#0dbb7d}.bg-yellow-600{background:#f38f19}.bg-blue-600{background:#00c0e9}.bg-red-600{background:#fb2b2b}.bg-important-600{background:#ff6f42}.bg-purple-600{background:#7055df}.bg-green-700{background:#0ca76f}.bg-yellow-700{background:#ed8307}.bg-blue-700{background:#1099ed}.bg-red-700{background:#d91b1b}.bg-important-700{background:#ff4912}.bg-purple-700{background:#513baa}.bg-green-900{background:#186c4e}.bg-yellow-900{background:#af5f01}.bg-blue-900{background:#046bab}.bg-red-900{background:#a20606}.bg-important-900{background:#611700}.bg-purple-900{background:#30216f}.bg-hover{background:#f6f6f6}.bg-active{background:#e6eaf1}.hl-tutorial{position:relative!important;z-index:1010!important;-webkit-box-shadow:0 0 0 0 #000!important;box-shadow:0 0 0 0 #000!important;-webkit-transition:-webkit-box-shadow 1s!important;-o-transition:box-shadow 1s!important;transition:-webkit-box-shadow 1s!important;transition:box-shadow 1s!important;transition:box-shadow 1s,-webkit-box-shadow 1s!important}.hl-tutorial.hl-in{-webkit-box-shadow:0 0 20px 0 #ffff8d,0 0 0 2px #ffd180,0 0 0 3000px rgba(0,0,0,.2)!important;box-shadow:0 0 20px 0 #ffff8d,0 0 0 2px #ffd180,0 0 0 3000px rgba(0,0,0,.2)!important}.btn.tooltip-tutorial,.hl-tutorial.hl-in:hover{position:relative!important;z-index:1010!important;-webkit-box-shadow:0 0 30px 0 #ffff8d,0 0 0 5px #ffd180,0 0 0 3000px rgba(0,0,0,.3)!important;box-shadow:0 0 30px 0 #ffff8d,0 0 0 5px #ffd180,0 0 0 3000px rgba(0,0,0,.3)!important}.tooltip-max .tooltip-inner{max-width:1000px;padding:8px 10px}.transition-all *{-webkit-transition:all .2s!important;-o-transition:all .2s!important;transition:all .2s!important}.scroll-x{overflow-x:auto!important}.scroll-y{overflow-y:auto!important}.divider+.divider{display:none}.ie *{-webkit-transition:none!important;-o-transition:none!important;transition:none!important}@font-face{font-family:Oswald;font-weight:400;src:url(../fonts/Oswald-Regular.ttf)}@font-face{font-family:Oswald;font-weight:500;src:url(../fonts/Oswald-Medium.ttf)}@font-face{font-family:Oswald;font-weight:300;src:url(../fonts/Oswald-Light.ttf)}.num{font-family:Oswald;font-weight:400}@font-face{font-family:ZentaoIcon;font-style:normal;font-weight:400;src:url(../fonts/ZentaoIcon.eot?v=1.34);src:url(../fonts/ZentaoIcon.eot?#iefix&v=1.34) format('embedded-opentype'),url(../fonts/ZentaoIcon.woff?v=1.34) format('woff'),url(../fonts/ZentaoIcon.ttf?v=1.34) format('truetype'),url(../fonts/ZentaoIcon.svg#regular?v=1.34) format('svg')}.icon,[class*=" icon-"],[class^=icon-]{font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-transform:none;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon:before{display:inline-block;min-width:14px;text-align:center}a .icon,a [class*=" icon-"],a [class^=icon-]{display:inline}.icon-lg:before{font-size:1.33333333em;vertical-align:-10%}.icon-2x{font-size:28px}.icon-3x{font-size:42px}.icon-4x{font-size:56px}.icon-5x{font-size:70px}.icon-spin{display:inline-block;-webkit-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}a .icon-spin{display:inline-block;text-decoration:none}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0);transform:rotate(0)}100%{-o-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);-o-transform:rotate(359deg);transform:rotate(359deg)}}.icon-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.icon-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.icon-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.icon-flip-horizontal{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);-o-transform:scale(-1,1);transform:scale(-1,1)}.icon-flip-vertical{-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);-o-transform:scale(1,-1);transform:scale(1,-1)}.icon.icon-flip-horizontal,.icon.icon-flip-vertical,.icon.icon-rotate-180,.icon.icon-rotate-270,.icon.icon-rotate-90{display:inline-block}.icon-zentao:before{content:"\e901"}.icon-zentao-alt:before{content:"\e900"}.icon-help:before{content:"\e968"}.icon-import:before{content:"\e904"}.icon-download:before{content:"\e904"}.icon-export:before{content:"\e905"}.icon-lightbulb:before{content:"\e91c"}.icon-close:before{content:"\e936"}.icon-check:before{content:"\e5ca"}.icon-plus:before{content:"\e925"}.icon-minus:before{content:"\e926"}.icon-expand-alt:before{content:"\e6f1"}.icon-collapse-alt:before{content:"\e6f2"}.icon-fullscreen:before{content:"\e96b"}.icon-star-empty:before{content:"\e94a"}.icon-star:before{content:"\e94b"}.icon-exclamation-sign:before{content:"\e930"}.icon-info-sign:before{content:"\e9d5"}.icon-flag:before{content:"\e937"}.icon-check-circle:before{content:"\e92f"}.icon-check-sign:before{content:"\e938"}.icon-chart-pie:before{content:"\e95b"}.icon-history:before{content:"\e95f"}.icon-pencil:before{content:"\e254"}.icon-search:before{content:"\e928"}.icon-restart:before{content:"\e95e"}.icon-cog:before{content:"\e93b"}.icon-chart-line:before{content:"\e95c"}.icon-chart-bar:before{content:"\e95d"}.icon-bar-chart:before{content:"\e95d"}.icon-exchange:before{content:"\e927"}.icon-severity:before{content:"\e973"}.icon-book:before{content:"\f02d"}.icon-treemap-alt:before{content:"\e971"}.icon-severity-solid:before{content:"\e902"}.icon-chat-line:before{content:"\e998"}.icon-stack:before{content:"\e943"}.icon-cube:before{content:"\e967"}.icon-minus-sign:before{content:"\e939"}.icon-bars-sign:before{content:"\e93a"}.icon-chat:before{content:"\e940"}.icon-message:before{content:"\e940"}.icon-more:before{content:"\e744"}.icon-certificate:before{content:"\f0a3"}.icon-bell:before{content:"\e7f5"}.icon-columns:before{content:"\f0db"}.icon-envelope-o:before{content:"\e92a"}.icon-unfold-all:before{content:"\e931"}.icon-fold-all:before{content:"\e932"}.icon-bars:before{content:"\e948"}.icon-cards-view:before{content:"\e949"}.icon-ellipsis-v:before{content:"\e5d4"}.icon-spinner-indicator:before{content:"\e982"}.icon-up-circle:before{content:"\e92b"}.icon-right-circle:before{content:"\e92c"}.icon-down-circle:before{content:"\e92d"}.icon-left-circle:before{content:"\e92e"}.icon-angle-double-right:before{content:"\f101"}.icon-angle-down:before{content:"\e313"}.icon-angle-left:before{content:"\e314"}.icon-angle-right:before{content:"\e315"}.icon-angle-top:before{content:"\e316"}.icon-first-page:before{content:"\e5dc"}.icon-last-page:before{content:"\e5dd"}.icon-caret-down:before{content:"\f0d7"}.icon-caret-up:before{content:"\f0d8"}.icon-caret-left:before{content:"\f0d9"}.icon-caret-right:before{content:"\f0da"}.icon-sort:before{content:"\f0dc"}.icon-sort-down:before{content:"\f0dd"}.icon-sort-up:before{content:"\f0de"}.icon-arrow-up:before{content:"\e923"}.icon-arrow-down:before{content:"\e924"}.icon-arrow-left:before{content:"\e952"}.icon-arrow-right:before{content:"\e93e"}.icon-chevron-left:before{content:"\e934"}.icon-chevron-right:before{content:"\e935"}.icon-chevron-double-up:before{content:"\e959"}.icon-chevron-double-down:before{content:"\e95a"}.icon-folder-account:before{content:"\e942"}.icon-folder-move:before{content:"\e960"}.icon-folder-plus:before{content:"\e961"}.icon-folder-upload:before{content:"\e962"}.icon-folder-star:before{content:"\e963"}.icon-folder-edit:before{content:"\e964"}.icon-folder-download:before{content:"\e965"}.icon-folder-outline:before{content:"\e966"}.icon-folder:before{content:"\e944"}.icon-folder-o:before{content:"\e945"}.icon-folder-open-o:before{content:"\e946"}.icon-folder-open:before{content:"\e947"}.icon-color:before{content:"\e93c"}.icon-paper-clip:before{content:"\e93d"}.icon-text:before{content:"\e929"}.icon-share:before{content:"\f064"}.icon-format-list-bulleted:before{content:"\e9a8"}.icon-format-bold:before{content:"\e953"}.icon-format-header-pound:before{content:"\e954"}.icon-format-italic:before{content:"\e955"}.icon-format-list-numbers:before{content:"\e969"}.icon-format-quote-close:before{content:"\e96a"}.icon-image:before{content:"\e96c"}.icon-table-large:before{content:"\e96d"}.icon-aiux:before{content:"\e99e"}.icon-qc:before{content:"\e986"}.icon-qc-q:before{content:"\e985"}.icon-qc-c:before{content:"\e987"}.icon-sonarqube:before{content:"\e9ba"}.icon-college:before{content:"\e9c8"}.icon-ztool:before{content:"\e9c1"}.icon-contacts:before{content:"\e9c3"}.icon-chats:before{content:"\e9c4"}.icon-menu-my:before{content:"\e97a"}.icon-home:before{content:"\e97a"}.icon-program:before{content:"\e9aa"}.icon-lightbulb-alt:before{content:"\e98f"}.icon-product:before{content:"\e98f"}.icon-rocket:before{content:"\e99c"}.icon-project:before{content:"\e99c"}.icon-run:before{content:"\e9a9"}.icon-test:before{content:"\e956"}.icon-infinite:before{content:"\e9a3"}.icon-devops:before{content:"\e9a3"}.icon-ops:before{content:"\e903"}.icon-doc:before{content:"\e99b"}.icon-menu-doc:before{content:"\e99b"}.icon-statistic:before{content:"\e999"}.icon-menu-backend:before{content:"\e993"}.icon-assets:before{content:"\e9ae"}.icon-diamond:before{content:"\e9ae"}.icon-feedback:before{content:"\e991"}.icon-flow:before{content:"\e994"}.icon-oa:before{content:"\e9a1"}.icon-more-circle:before{content:"\e988"}.icon-controls:before{content:"\e995"}.icon-account:before{content:"\e992"}.icon-about:before{content:"\e996"}.icon-info:before{content:"\e996"}.icon-cog-outline:before{content:"\e997"}.icon-backend:before{content:"\e997"}.icon-exit:before{content:"\e99a"}.icon-theme:before{content:"\e9a0"}.icon-globe:before{content:"\f0ac"}.icon-lang:before{content:"\f0ac"}.icon-table-sort:before{content:"\e9e1"}.icon-blame:before{content:"\e9e0"}.icon-draft-edit:before{content:"\e9dd"}.icon-sub-review:before{content:"\e9df"}.icon-sub-review-user:before{content:"\e9db"}.icon-ztf:before{content:"\e9dc"}.icon-save:before{content:"\e9d8"}.icon-list-box:before{content:"\e9b4"}.icon-usecase:before{content:"\e99d"}.icon-code:before{content:"\e990"}.icon-summary:before{content:"\e9ad"}.icon-more-alt:before{content:"\e9a7"}.icon-customer:before{content:"\e9d9"}.icon-ticket:before{content:"\e9e1"}.icon-gantt-alt:before{content:"\e9e2"}.icon-appose:before{content:"\e9e2"}.icon-inline:before{content:"\e9e3"}.icon-tree:before{content:"\e9c9"}.icon-list:before{content:"\e9cb"}.icon-gantt:before{content:"\e9cc"}.icon-group-view:before{content:"\e9cd"}.icon-inherit-space:before{content:"\e9c2"}.icon-card-archive:before{content:"\e9b8"}.icon-col-archive:before{content:"\e9b9"}.icon-col-add-right:before{content:"\e9bb"}.icon-col-add-left:before{content:"\e9bc"}.icon-col-split:before{content:"\e9bd"}.icon-waterfall:before{content:"\e9a4"}.icon-manual:before{content:"\e98d"}.icon-kanban:before{content:"\e983"}.icon-lane:before{content:"\e9b1"}.icon-snap-house:before{content:"\e9e3"}.icon-wrench:before{content:"\e9e4"}.icon-desktop:before{content:"\e9e5"}.icon-back:before{content:"\e9d3"}.icon-back-circle:before{content:"\e9da"}.icon-shield:before{content:"\e9ca"}.icon-meh:before{content:"\e9ce"}.icon-frown:before{content:"\e9cf"}.icon-smile:before{content:"\e9d0"}.icon-unlock-solid:before{content:"\e9d1"}.icon-lock-solid:before{content:"\e9d2"}.icon-ver:before{content:"\e9c6"}.icon-publish:before{content:"\e9c7"}.icon-send:before{content:"\e9c7"}.icon-tag:before{content:"\e9be"}.icon-tag-lock:before{content:"\e9bf"}.icon-code-fork:before{content:"\f126"}.icon-branch-lock:before{content:"\e9c0"}.icon-groups:before{content:"\e9af"}.icon-thumbs-up:before{content:"\f087"}.icon-thumbs-down:before{content:"\f088"}.icon-thumbs-up-solid:before{content:"\e9d6"}.icon-thumbs-down-solid:before{content:"\e9d7"}.icon-hash:before{content:"\e9ab"}.icon-version:before{content:"\e9ab"}.icon-p-square:before{content:"\e97b"}.icon-video-play:before{content:"\e97f"}.icon-plus-solid-circle:before{content:"\e974"}.icon-minuse-solid-circle:before{content:"\e9b6"}.icon-s:before{content:"\e975"}.icon-c:before{content:"\e976"}.icon-t:before{content:"\e977"}.icon-guide:before{content:"\e978"}.icon-todo:before{content:"\e979"}.icon-side-left:before{content:"\e9b3"}.icon-side-right:before{content:"\e9b2"}.icon-fullscreen-exit:before{content:"\e972"}.icon-alert:before{content:"\e99f"}.icon-undo:before{content:"\e93f"}.icon-redo:before{content:"\e9d4"}.icon-swap:before{content:"\e9b0"}.icon-chat-solid:before{content:"\e9b5"}.icon-clock:before{content:"\e97c"}.icon-cost:before{content:"\e97d"}.icon-pencil-alt:before{content:"\e984"}.icon-size-height:before{content:"\e9c5"}.icon-file-log:before{content:"\e9de"}.icon-rich-text:before{content:"\e913"}.icon-markdown:before{content:"\e916"}.icon-excel:before{content:"\e933"}.icon-text-link:before{content:"\e94d"}.icon-ppt:before{content:"\e957"}.icon-word:before{content:"\e958"}.icon-doc-lib:before{content:"\e96f"}.icon-file:before{content:"\f016"}.icon-file-empty:before{content:"\f016"}.icon-file-text:before{content:"\f0f6"}.icon-file-alt:before{content:"\f15b"}.icon-file-text-alt:before{content:"\f15c"}.icon-file-pdf:before{content:"\f1c1"}.icon-file-word:before{content:"\f1c2"}.icon-file-excel:before{content:"\f1c3"}.icon-file-powerpoint:before{content:"\f1c4"}.icon-file-image:before{content:"\f1c5"}.icon-file-archive:before{content:"\f1c6"}.icon-file-audio:before{content:"\f1c7"}.icon-file-video:before{content:"\f1c8"}.icon-file-code:before{content:"\f1c9"}.icon-menu-collapse:before{content:"\e980"}.icon-menu-expand:before{content:"\e981"}.icon-group:before{content:"\e97e"}.icon-menu-users:before{content:"\e97e"}.icon-persons:before{content:"\e97e"}.icon-team:before{content:"\e97e"}.icon-estimate:before{content:"\e9ac"}.icon-sprint:before{content:"\e9a2"}.icon-shield-check:before{content:"\e9a5"}.icon-ok:before{content:"\e9a6"}.icon-printer:before{content:"\e906"}.icon-bullhorn:before{content:"\e910"}.icon-person:before{content:"\e941"}.icon-fields:before{content:"\e989"}.icon-trigger:before{content:"\e98a"}.icon-layout:before{content:"\e98b"}.icon-audit:before{content:"\e98c"}.icon-cancel:before{content:"\e951"}.icon-ban-circle:before{content:"\e951"}.icon-eye:before{content:"\e94e"}.icon-eye-off:before{content:"\e96e"}.icon-unlock:before{content:"\e94f"}.icon-lock:before{content:"\e950"}.icon-private:before{content:"\e950"}.icon-move:before{content:"\e94c"}.icon-hand-right:before{content:"\e907"}.icon-checked:before{content:"\e908"}.icon-off:before{content:"\e909"}.icon-start:before{content:"\e90a"}.icon-play:before{content:"\e90a"}.icon-time:before{content:"\e90b"}.icon-edit:before{content:"\e90c"}.icon-trash:before{content:"\e90d"}.icon-link:before{content:"\e90e"}.icon-unlink:before{content:"\e90f"}.icon-bug:before{content:"\e911"}.icon-list-alt:before{content:"\e912"}.icon-change:before{content:"\e970"}.icon-alter:before{content:"\e970"}.icon-glasses:before{content:"\e914"}.icon-review:before{content:"\e914"}.icon-sitemap:before{content:"\e915"}.icon-testcase:before{content:"\e915"}.icon-pluses:before{content:"\e917"}.icon-report-list:before{content:"\e918"}.icon-magic:before{content:"\e919"}.icon-active:before{content:"\e919"}.icon-treemap:before{content:"\e91a"}.icon-confirm:before{content:"\e91b"}.icon-split:before{content:"\e98e"}.icon-delay:before{content:"\e91d"}.icon-calendar:before{content:"\e91d"}.icon-pause:before{content:"\e91e"}.icon-ban:before{content:"\e91f"}.icon-plus-bold:before{content:"\e920"}.icon-copy:before{content:"\e921"}.icon-refresh:before{content:"\e922"}.icon-diff:before{content:"\e9b7"}.icon-sm:before{font-size:14px;vertical-align:10%}.icon-qc{position:relative}.icon-qc:before{width:1em;color:#7cb938;content:"\e985"}.icon-qc:after{position:absolute;top:0;left:0;width:1em;height:1em;font-family:ZentaoIcon;font-size:14px;font-size:inherit;font-style:normal;font-weight:400;font-variant:normal;line-height:1;color:#36a742;text-transform:none;content:"\e987";speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-message.has-dot{position:relative}.icon-message.has-dot::after{position:absolute;top:-3px;right:-5px;display:block;width:6px;height:6px;content:' ';background-color:#fc5959;border-radius:50%}.icon-project{-webkit-transform:scale(1.2);-ms-transform:scale(1.2);-o-transform:scale(1.2);transform:scale(1.2)}.icon-product{-webkit-transform:scale(1.15);-ms-transform:scale(1.15);-o-transform:scale(1.15);transform:scale(1.15)}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:13px;font-weight:400;line-height:18px;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;border-radius:4px;-webkit-transition:.4s cubic-bezier(.175,.885,.32,1);-o-transition:.4s cubic-bezier(.175,.885,.32,1);transition:.4s cubic-bezier(.175,.885,.32,1);-webkit-transition-property:background,border,outline,opacity,-webkit-box-shadow;-o-transition-property:background,border,box-shadow,outline,opacity;transition-property:background,border,outline,opacity,-webkit-box-shadow;transition-property:background,border,box-shadow,outline,opacity;transition-property:background,border,box-shadow,outline,opacity,-webkit-box-shadow}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:focus,.btn:hover{color:#313c52;text-decoration:none}.btn:active{text-decoration:none;background-image:none;outline:0;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.1)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:grayscale(1);-webkit-box-shadow:none;box-shadow:none;opacity:.5;-webkit-filter:grayscale(1)}.btn{color:#313c52;background-color:#fff;border-color:#d8dbde}.btn.active,.btn.hover,.btn:active,.btn:focus,.btn:hover,.open .dropdown-toggle.btn{color:#313c52;background-color:rgba(255,255,255,.8);border-color:#bcc2c7}.btn.active,.btn:active,.open .dropdown-toggle.btn{background-color:#f2f2f2;background-image:none;border-color:#bcc2c7}.btn.disabled,.btn.disabled.active,.btn.disabled:active,.btn.disabled:focus,.btn.disabled:hover,.btn[disabled],.btn[disabled].active,.btn[disabled]:active,.btn[disabled]:focus,.btn[disabled]:hover,fieldset[disabled] .btn,fieldset[disabled] .btn.active,fieldset[disabled] .btn:active,fieldset[disabled] .btn:focus,fieldset[disabled] .btn:hover{color:rgba(49,60,82,.3);background-color:#fff;border-color:#d8dbde}.btn-gray{color:#82899f;background-color:#f1f1f1;border-color:#f1f1f1}.btn-gray.active,.btn-gray.hover,.btn-gray:active,.btn-gray:focus,.btn-gray:hover,.open .dropdown-toggle.btn-gray{color:#82899f;background-color:rgba(241,241,241,.8);border-color:#d8d8d8}.btn-gray.active,.btn-gray:active,.open .dropdown-toggle.btn-gray{background-color:#e4e4e4;background-image:none;border-color:#d8d8d8}.btn-gray.disabled,.btn-gray.disabled.active,.btn-gray.disabled:active,.btn-gray.disabled:focus,.btn-gray.disabled:hover,.btn-gray[disabled],.btn-gray[disabled].active,.btn-gray[disabled]:active,.btn-gray[disabled]:focus,.btn-gray[disabled]:hover,fieldset[disabled] .btn-gray,fieldset[disabled] .btn-gray.active,fieldset[disabled] .btn-gray:active,fieldset[disabled] .btn-gray:focus,fieldset[disabled] .btn-gray:hover{color:rgba(130,137,159,.3);background-color:#f1f1f1;border-color:#f1f1f1}.btn-primary{color:#fff;background-color:#2e7fff;border-color:transparent}.btn-primary.active,.btn-primary.hover,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open .dropdown-toggle.btn-primary{color:#fff;background-color:rgba(46,127,255,.8);border-color:rgba(0,0,0,0)}.btn-primary.active,.btn-primary:active,.open .dropdown-toggle.btn-primary{background-color:#156fff;background-image:none;border-color:rgba(0,0,0,0)}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{color:rgba(255,255,255,.3);background-color:#2e7fff;border-color:transparent}.btn-secondary{color:#fff;background-color:#18a6fd;border-color:transparent}.btn-secondary.active,.btn-secondary.hover,.btn-secondary:active,.btn-secondary:focus,.btn-secondary:hover,.open .dropdown-toggle.btn-secondary{color:#fff;background-color:rgba(24,166,253,.8);border-color:rgba(0,0,0,0)}.btn-secondary.active,.btn-secondary:active,.open .dropdown-toggle.btn-secondary{background-color:#029bf9;background-image:none;border-color:rgba(0,0,0,0)}.btn-secondary.disabled,.btn-secondary.disabled.active,.btn-secondary.disabled:active,.btn-secondary.disabled:focus,.btn-secondary.disabled:hover,.btn-secondary[disabled],.btn-secondary[disabled].active,.btn-secondary[disabled]:active,.btn-secondary[disabled]:focus,.btn-secondary[disabled]:hover,fieldset[disabled] .btn-secondary,fieldset[disabled] .btn-secondary.active,fieldset[disabled] .btn-secondary:active,fieldset[disabled] .btn-secondary:focus,fieldset[disabled] .btn-secondary:hover{color:rgba(255,255,255,.3);background-color:#18a6fd;border-color:transparent}.btn-warning{color:#fff;background-color:#ff9f46;border-color:transparent}.btn-warning.active,.btn-warning.hover,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open .dropdown-toggle.btn-warning{color:#fff;background-color:rgba(255,159,70,.8);border-color:rgba(0,0,0,0)}.btn-warning.active,.btn-warning:active,.open .dropdown-toggle.btn-warning{background-color:#ff922c;background-image:none;border-color:rgba(0,0,0,0)}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{color:rgba(255,255,255,.3);background-color:#ff9f46;border-color:transparent}.btn-danger{color:#fff;background-color:#fc5959;border-color:transparent}.btn-danger.active,.btn-danger.hover,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open .dropdown-toggle.btn-danger{color:#fff;background-color:rgba(252,89,89,.8);border-color:rgba(0,0,0,0)}.btn-danger.active,.btn-danger:active,.open .dropdown-toggle.btn-danger{background-color:#fc4040;background-image:none;border-color:rgba(0,0,0,0)}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{color:rgba(255,255,255,.3);background-color:#fc5959;border-color:transparent}.btn-success{color:#fff;background-color:#22c98d;border-color:transparent}.btn-success.active,.btn-success.hover,.btn-success:active,.btn-success:focus,.btn-success:hover,.open .dropdown-toggle.btn-success{color:#fff;background-color:rgba(34,201,141,.8);border-color:rgba(0,0,0,0)}.btn-success.active,.btn-success:active,.open .dropdown-toggle.btn-success{background-color:#1eb37e;background-image:none;border-color:rgba(0,0,0,0)}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{color:rgba(255,255,255,.3);background-color:#22c98d;border-color:transparent}.btn-info{color:#2e7fff;background-color:#e6f0ff;border-color:transparent}.btn-info.active,.btn-info.hover,.btn-info:active,.btn-info:focus,.btn-info:hover,.open .dropdown-toggle.btn-info{color:#2e7fff;background-color:rgba(230,240,255,.8);border-color:rgba(0,0,0,0)}.btn-info.active,.btn-info:active,.open .dropdown-toggle.btn-info{background-color:#cce1ff;background-image:none;border-color:rgba(0,0,0,0)}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{color:rgba(46,127,255,.3);background-color:#e6f0ff;border-color:transparent}.btn-link{padding-right:6px;padding-left:6px;font-weight:400;color:#313c52;text-shadow:none;cursor:pointer;background:0 0;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover,.btn-link[disabled],fieldset[disabled] .btn-link{border-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link:focus,.btn-link:hover{color:#0b0f18;background:#f1f1f1;background:rgba(0,0,0,.075)}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#c4c4c4;text-decoration:none}.btn-lg{padding:11px 16px;font-size:14px;line-height:18px;border-radius:4px}.btn-mini,.btn-sm{padding:3px 8px;font-size:12px;line-height:18px;border-radius:4px}.btn-mini,.btn-xs{padding:0 5px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.btn-wide{min-width:120px}.btn-limit{max-width:180px;padding-right:20px;overflow:hidden;text-overflow:unset;white-space:nowrap}.btn-limit>.caret{position:absolute;top:14px;right:8px}.btn-circle{border-radius:17px}.btn>.label-icon{top:3px;padding:3px;margin:-2px;background-color:rgba(0,0,0,.2);border-radius:12px}.btn>.label-icon>.icon{font-size:16px;line-height:18px}.btn>.icon+.text{margin-left:5px}.btn.btn-sm.btn-circle{border-radius:12px}.btn.btn-sm>.label-icon{top:2px;width:20px;height:20px;padding:1px;line-height:20px}.btn.btn-sm>.label-icon>.icon{position:relative;top:-1px;display:inline-block;font-size:14px;line-height:18px}.btn-icon-left{position:relative;padding-left:35px;overflow:hidden;text-align:right}.btn-icon-left>.label-icon{position:absolute;left:5px;margin:0}.btn-icon-left>.icon{position:absolute;top:0;bottom:0;left:0;display:block;width:30px;line-height:30px;color:#18a6fd;text-align:center;background:#e6f0ff}.btn-icon-left.btn-sm{padding-left:28px}.btn-icon-left.btn-sm>.label-icon{left:2px}.btn-icon-left.btn-sm>.icon{width:24px;line-height:24px}.btn-icon-right{position:relative;padding-right:35px;text-align:left}.btn-icon-right>.label-icon{position:absolute;right:5px;margin:0}.btn-icon-right.btn-sm{padding-right:28px}.btn-icon-right.btn-sm>.label-icon{right:2px}.btn-icon{min-width:32px;padding-right:0;padding-left:0}.btn-icon.btn-sm{width:24px;min-width:24px;height:24px}.btn-group{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group>.btn-group{float:left}.btn-group>.btn{border-radius:0}.btn-group>.btn:first-child{border-top-left-radius:2px;border-bottom-left-radius:2px}.btn-group>.btn:last-child{border-top-right-radius:2px;border-bottom-right-radius:2px}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.dropdown{float:left;margin-right:10px}.btn-toolbar>.btn-group:last-child,.btn-toolbar>.btn:last-child,.btn-toolbar>.dropdown:last-child{margin-right:0}.btn-toolbar>.divider{float:left;height:20px;margin:7px 5px 4px 10px;border-left:1px solid rgba(0,0,0,.1)}.btn-toolbar .space{float:left;min-height:1px;margin:0 10px 0 10px}.btn-toolbar .input-control{float:left;width:120px}.btn-toolbar .page-title{float:left;line-height:34px}.btn-toolbar .page-title .text{font-size:14px;font-weight:700}.btn-toolbar .page-title .label{top:-2px;margin-right:10px}.btn-toolbar .divider+.page-title{margin-left:15px}.btn-active-line{position:relative;font-weight:700;color:#2e7fff}.btn-active-line:after{position:absolute;right:5px;bottom:1px;left:5px;display:block;content:' ';border-bottom:2px solid #2e7fff}.btn-active-line:hover{color:#2e7fff}.btn-active-text .text{position:relative;top:-1px;display:inline-block;font-weight:700;color:#1e6aeb}.btn-active-text .text:after{position:absolute;bottom:-5px;display:block;width:100%;content:' ';border-bottom:2px solid #2e7fff}.angle-btn{position:relative;padding:1px;background:#fff;border:1px solid #c4c4c4;border-right:none}.angle-btn:first-child{border-radius:2px 0 0 2px}.btn-toolbar>.angle-btn{margin-right:8px}.angle-btn:after,.angle-btn:before{position:absolute;top:-1px;right:-8px;display:block;width:0;height:0;content:' ';border-color:transparent transparent transparent #c4c4c4;border-style:solid;border-width:17px 0 17px 8px}.angle-btn:after{right:-7px;border-color:transparent transparent transparent #fff;border-radius:2px}.angle-btn .btn{padding:6px;font-weight:700;background:#fff;border:none;border-radius:4px!important}.angle-btn .btn.btn-limit{padding-right:16px}.angle-btn .btn.btn-limit>.caret{right:4px}.angle-btn+.angle-btn{border-left:none}.angle-btn+.angle-btn>.btn-group:first-child{padding-left:8px}.angle-btn+.angle-btn>.btn-group:first-child:after,.angle-btn+.angle-btn>.btn-group:first-child:before{position:absolute;top:-2px;left:0;display:block;width:0;height:0;content:' ';border-color:transparent transparent transparent #c4c4c4;border-style:solid;border-width:17px 0 17px 8px}.angle-btn+.angle-btn>.btn-group:first-child:after{left:-1px;border-color:transparent transparent transparent #fff;border-width:17px 0 17px 8px}.btn-toolbar>.angle-btn.active,.btn-toolbar>.angle-btn:last-child{border-color:#2e7fff}.btn-toolbar>.angle-btn.active .btn,.btn-toolbar>.angle-btn:last-child .btn{color:#2e7fff}.btn-toolbar>.angle-btn.active:after,.btn-toolbar>.angle-btn.active:before,.btn-toolbar>.angle-btn:last-child:after,.btn-toolbar>.angle-btn:last-child:before{border-color:transparent transparent transparent #2e7fff}.btn-toolbar>.angle-btn.active:after,.btn-toolbar>.angle-btn:last-child:after{border-color:transparent transparent transparent #fff}.btn-toolbar>.angle-btn+.angle-btn:last-child>.btn-group:first-child:after,.btn-toolbar>.angle-btn+.angle-btn:last-child>.btn-group:first-child:before,.btn-toolbar>.angle-btn.active+.angle-btn>.btn-group:first-child:after,.btn-toolbar>.angle-btn.active+.angle-btn>.btn-group:first-child:before{border-color:transparent transparent transparent #2e7fff}.btn-toolbar>.angle-btn+.angle-btn:last-child>.btn-group:first-child:after,.btn-toolbar>.angle-btn.active+.angle-btn>.btn-group:first-child:after{border-color:transparent transparent transparent #fff}.btn-toolbar>.angle-btn.active+.angle-btn,.btn-toolbar>.angle-btn.normal{border-color:#c4c4c4}.btn-toolbar>.angle-btn.active+.angle-btn .btn,.btn-toolbar>.angle-btn.normal .btn{color:#313c52}.btn-toolbar>.angle-btn.active+.angle-btn:after,.btn-toolbar>.angle-btn.active+.angle-btn:before,.btn-toolbar>.angle-btn.normal:after,.btn-toolbar>.angle-btn.normal:before{border-color:transparent transparent transparent #c4c4c4}.btn-toolbar>.angle-btn.active+.angle-btn:after,.btn-toolbar>.angle-btn.normal:after{border-color:transparent transparent transparent #fff}.btn-toolbar>.angle-btn.active+.angle-btn>.btn-group:first-child:before,.btn-toolbar>.angle-btn.normal>.btn-group:first-child:before{border-color:transparent transparent transparent #c4c4c4!important}.nav>li>.btn.btn-primary{color:#fff}.nav>li>.btn.btn-primary:focus,.nav>li>.btn.btn-primary:hover{background:rgba(46,127,255,.8)}.btn.btn-action,.c-actions .btn{display:inline-block;width:26px;padding:2px;overflow:hidden;line-height:20px;color:#18a6fd;background:0 0;border-color:transparent}.btn.btn-action>i,.c-actions .btn>i{position:relative;top:1px;font-size:18px}.btn.btn-action:hover,.c-actions .btn:hover{color:#2e7fff;background-color:#cce1ff}.c-actions .btn+.btn{margin-left:-4px}.label{position:relative;display:inline-block;padding:3px 5px;font-size:12px;font-weight:400;vertical-align:middle;border-radius:2px}.label+.label{margin-left:4px}.label-pale{background:#b3d1ff!important}.label-badge{border-radius:9px}.label-light{color:#3c4353;background-color:#ddd}.label-primary{background:#2e7fff!important}.label-gray{color:#878da0;background:#e8ebef}.label-outline.label-danger{color:#fc5959;background:#ffebeb;border-color:rgba(252,89,89,.25)}.label-outline.label-light{color:#838a9d;background:#f2f5fb;border-color:#e1e5ee}.label-primary.label-outline{background:#e6f0ff!important;border-color:rgba(46,127,255,.25)}.label-outline.label-success{background:#ecfff8;border-color:rgba(34,201,141,.25)}.label-outline.label-info{background:#e7f6ff;border-color:rgba(55,178,254,.25)}.label-outline.label-warning{background:#fff4ea;border-color:rgba(255,159,70,.25)}.label-dot{position:relative;top:-1px;padding:0;border-radius:50%}.label-dot+.status-text{display:inline-block;margin-left:5px}.label-icon{min-width:18px;padding:0;line-height:18px;border-radius:10px}.label-id{display:inline-block;min-width:30px;padding:0 5px;font-size:12px;line-height:16px;color:#838a9d;text-align:center;vertical-align:middle;background-color:transparent;border:1px solid #838a9d;border-radius:2px}.pri-1,.todo-pri-1{color:#fc5959}[class*=" status-"],[class^=status-]{color:#313c52}.status-changed,.status-delayed,.status-doing,.status-fail,.status-investigate{color:#fc5959}.status-changed>.label-dot,.status-delayed>.label-dot,.status-doing>.label-dot,.status-fail>.label-dot,.status-investigate>.label-dot{background-color:#fc5959}.status-wait{color:#838a9d}.status-wait>.label-dot{background-color:#7ec5ff}.status-unclosed{color:#838a9d}.status-unclosed>.label-dot{background-color:#2e7fff}.status-done,.status-normal,.status-pass,.status-resolved{color:#43a047}.status-done>.label-dot,.status-normal>.label-dot,.status-pass>.label-dot,.status-resolved>.label-dot{background-color:#22c98d}.status-postpone{color:#838a9d}.status-postpone>.label-dot{background-color:#fc5959}.status-blocked{position:relative;left:-5px;display:inline-block;padding:0 5px;line-height:20px;color:#313c52;background:0 0;border-radius:10px}.status-blocked>.label-dot{background-color:#ff9f46}.status-pause,.status-suspended{color:#ff9f46}.status-pause>.label-dot,.status-suspended>.label-dot{background-color:#ff9f46}.status-active.status-bug,.status-draft{color:#8166ee}.status-active.status-bug>.label-dot,.status-draft>.label-dot{background-color:#8166ee}.status-closed,.status-terminate{color:#838a9d}.status-closed>.label-dot,.status-terminate>.label-dot{background-color:#838a9d}.status-cancel{color:#838a9d}.status-cancel>.label-dot{background-color:#c4c4c4}.label-pri{display:inline-block;min-width:18px;max-width:100%;height:18px;padding:0 4px;overflow:hidden;font-size:12px;line-height:16px;color:#838a9d;text-align:center;text-overflow:clip;white-space:normal;vertical-align:middle;border:1px solid #838a9d;border-radius:10px}.label-pri-1,.label-selector>.label-pri[data-value="1"]{overflow:visible;color:#ff6f42;border-color:#ff6f42}.label-pri-2,.label-selector>.label-pri[data-value="2"]{color:#f38f19;border-color:#f38f19}.label-pri-3,.label-selector>.label-pri[data-value="3"]{color:#37b2fe;border-color:#37b2fe}.label-pri-4,.label-selector>.label-pri[data-value="4"]{color:#22c98d;border-color:#22c98d}.label-pri-5,.label-selector>.label-pri[data-value="5"]{color:#838a9d;border-color:#838a9d}.label-pri-0,.label-selector>.label-pri.active[data-value="0"]{color:#d5d9df;border-color:#d5d9df}.label-severity{position:relative;display:inline-block;width:24px;height:20px;font-weight:bolder;text-align:center;vertical-align:middle}.label-severity:before{position:absolute;top:-3px;left:0;z-index:0;display:block;font-family:ZentaoIcon;font-size:14px;font-size:24px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;color:inherit;text-transform:none;content:"\e973";speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.label-severity:after{position:absolute;top:7px;left:0;z-index:1;display:block;width:24px;font-size:12px;line-height:12px;text-align:center;content:attr(data-severity)}.label-severity[data-severity],.label-severity[data-value]{color:#ccc}.label-severity[data-severity="0"],.label-severity[data-value="0"]{color:#ccc}.label-severity[data-severity="1"],.label-severity[data-value="1"]{color:#fb2b2b}.label-severity[data-severity="2"],.label-severity[data-value="2"]{color:#c92d79}.label-severity[data-severity="3"],.label-severity[data-value="3"]{color:#f48116}.label-severity[data-severity="4"],.label-severity[data-value="4"]{color:#ffba34}.label-severity[data-severity="5"],.label-severity[data-value="5"]{color:#9ea3b0}.label-severity-custom[data-severity]{color:#d5d9df}.label-severity-custom[data-severity="1"]{color:#c62828}.label-severity-custom[data-severity="2"]{color:#ff8f00}.label-severity-custom[data-severity="3"]{color:#fdd835}.label-severity-custom[data-severity="4"]{color:#cddc39}.label-severity-custom[data-severity="5"]{color:#8bc34a}.label-selector{padding:0 10px}.label-selector>.label{display:inline-block;min-width:24px;height:24px;padding:0 5px;font-size:14px;line-height:20px;text-align:center;cursor:pointer;background:0 0;border:2px solid #d5d9df;border-radius:15px}.label-selector>.label+.label{margin-left:10px}.label-selector>.label.empty{border-color:transparent}.label-selector>.label.label-severity{font-size:12px;line-height:28px;border-color:transparent}.label-selector>.label.label-severity:before{top:-2px;left:-2px}.label-selector>.label.label-severity:after{display:none}.label-selector>.label.label-severity.active{background:0 0;filter:none;-webkit-filter:none}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{margin-top:2px}.ie .checkbox input[type=checkbox],.ie .checkbox-inline input[type=checkbox],.ie .radio input[type=radio],.ie .radio-inline input[type=radio]{margin-top:4px}.is-firefox .checkbox input[type=checkbox],.is-firefox .checkbox-inline input[type=checkbox],.is-firefox .radio input[type=radio],.is-firefox .radio-inline input[type=radio]{margin-top:3px}.checkbox-primary,.radio-primary{position:relative;display:block;vertical-align:middle}.checkbox-primary.inline-block,.radio-primary.inline-block{display:inline-block}.checkbox-primary.inline-block+.inline-block,.radio-primary.inline-block+.inline-block{margin-left:15px}.checkbox-primary>input,.radio-primary>input{position:absolute;top:0;left:0;z-index:3;width:100%;height:100%;margin:0;opacity:0}.checkbox-primary>label,.radio-primary>label{display:block;height:20px;padding-left:20px;margin:0;font-weight:400;line-height:20px;cursor:pointer}.checkbox-primary>label:after,.checkbox-primary>label:before,.radio-primary>label:after,.radio-primary>label:before{position:absolute;top:3px;right:0;left:0;display:block;width:14px;height:14px;line-height:14px;text-align:center;content:' ';border-radius:3px}.checkbox-primary>label:after,.radio-primary>label:after{z-index:1;border:1px solid #d9dbe1;border-color:rgba(0,0,0,.15);-webkit-transition:.4s cubic-bezier(.175,.885,.32,1);-o-transition:.4s cubic-bezier(.175,.885,.32,1);transition:.4s cubic-bezier(.175,.885,.32,1);-webkit-transition-property:border,background-color;-o-transition-property:border,background-color;transition-property:border,background-color}.checkbox-primary>label:before,.radio-primary>label:before{top:3px;z-index:2;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-weight:900;font-variant:normal;line-height:1;text-transform:none;content:"\e5ca";opacity:0;-webkit-transition:.2s cubic-bezier(.175,.885,.32,1);-o-transition:.2s cubic-bezier(.175,.885,.32,1);transition:.2s cubic-bezier(.175,.885,.32,1);-webkit-transition-property:opacity,-webkit-transform;-o-transition-property:opacity,-o-transform;transition-property:opacity,-webkit-transform;transition-property:opacity,transform;transition-property:opacity,transform,-webkit-transform,-o-transform;-webkit-transform:scale(0);-ms-transform:scale(0);-o-transform:scale(0);transform:scale(0);speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.checkbox-primary.checked>label:after,.checkbox-primary>input:checked+label:after,.radio-primary.checked>label:after,.radio-primary>input:checked+label:after{background-color:#22c98d;border-color:#22c98d;border-width:4px}.checkbox-primary.checked>label:before,.checkbox-primary>input:checked+label:before,.radio-primary.checked>label:before,.radio-primary>input:checked+label:before{color:#fff;opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}.checkbox-primary input:indeterminate+label:before,.checkbox-primary.indeterminate>label:before,.radio-primary input:indeterminate+label:before,.radio-primary.indeterminate>label:before{top:9px;left:3px;width:8px;height:2px;content:' ';background-color:#a3a2bc;opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}.checkbox-primary.focus>label:after,.checkbox-primary>input:focus+label:after,.radio-primary.focus>label:after,.radio-primary>input:focus+label:after{border-color:#22c98d;-webkit-box-shadow:0 0 0 3px rgba(34,201,141,.2);box-shadow:0 0 0 3px rgba(34,201,141,.2)}.checkbox-primary:hover>label:after,.radio-primary:hover>label:after{border-color:#22c98d}.checkbox-primary.checkbox-right>label,.radio-primary.checkbox-right>label{padding:0 30px 0 0}.checkbox-primary.checkbox-right>label:after,.checkbox-primary.checkbox-right>label:before,.radio-primary.checkbox-right>label:after,.radio-primary.checkbox-right>label:before{right:0;left:auto}.checkbox-primary input:disabled+label:after,.checkbox-primary.disabled>label:after,.radio-primary input:disabled+label:after,.radio-primary.disabled>label:after{background-color:#e5e5e5!important;border-color:#bbb!important}.checkbox-primary input:disabled:checked+label:after,.checkbox-primary.checked.disabled>label:after,.radio-primary input:disabled:checked+label:after,.radio-primary.checked.disabled>label:after{background-color:#bbb!important}.radio-primary>label:after{border-radius:50%}.radio-primary>label:before{top:7px;left:6px;width:6px;height:6px;content:' ';border:none;border-radius:50%}.radio-primary.checked>label:after,.radio-primary>input:checked+label:after{background-color:transparent;border-color:#22c98d;border-width:2px}.radio-primary.checked>label:before,.radio-primary>input:checked+label:before{background-color:#22c98d}.radio-primary input:disabled:checked+label:after,.radio-primary.checked.disabled>label:after{background-color:transparent;border-color:#bbb}.radio-primary input:disabled:checked+label:before,.radio-primary.checked.disabled>label:before{background-color:#bbb}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li>a{position:relative;border:none!important;border-radius:2px!important}.nav-tabs>li.active>a{font-weight:700;color:#313c52!important}.nav-tabs>li.active>a:before{position:absolute;right:0;bottom:-1px;left:0;display:block;height:2px;content:' ';background:#2e7fff}.nav-tabs-primary>li.active>a{color:#1e6aeb!important}.panel{position:relative;margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045);box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045)}.panel-body{padding:20px}.panel-body.has-table{padding:10px}.panel-body.has-table .table{margin-bottom:0;table-layout:fixed}.panel-heading{padding:12px 48px 12px 20px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading+.panel-body{padding-top:0}.panel-title{font-size:14px;font-weight:700;line-height:20px}.panel-title .label{top:-1px}.panel-actions{position:absolute;top:0;right:0;padding:7px 8px}.panel:hover .panel-actions{z-index:10}.panel-actions>li>a{display:inline-block;min-width:30px;padding:0 5px;line-height:30px;color:#a6aab8;text-align:center;border-radius:4px}.panel-actions>li>a:hover{color:#313c52;text-decoration:initial;background-color:#f1f1f1}.panel-actions .btn-icon{color:#a6aab8}.panel-actions .btn.text-primary{color:#2e7fff}.panel .empty-tip{padding:30px 10px 50px;font-size:14px;color:#838a9d;text-align:center}.progress-text-left{position:relative;margin:7px 0;margin-left:35px;overflow:visible}.progress-text-left .progress-text{position:absolute;top:-7px;left:-35px;display:block;width:35px;height:20px;padding-right:5px;line-height:20px;color:#838a9d;text-align:right}.chart-color{width:20px}.chart-color-dot{display:inline-block;width:10px;height:10px;border-radius:50%}.chart-row{margin-top:10px}.chart-row+.chart-row{padding-top:10px;border-top:1px solid #eee}.chart-wrapper{padding:10px 5px;background:#eee}.chart-wrapper>h4{margin:5px 0 10px}.table-wrapper{max-height:250px;overflow:auto}.table-wrapper .table{margin:0}.progress-pie{position:relative}.progress-pie canvas{display:block}.progress-pie .progress-info{position:absolute;top:0;left:0;width:100%;height:100%;padding-top:25px;text-align:center}.progress-pie .progress-info>small{display:block;line-height:14px;color:#a6aab8}.progress-pie .progress-info>strong{display:block;font-size:36px;line-height:40px}.progress-pie .progress-info>strong>small{font-size:20px}.progress-pie-120 .progress-info{padding-top:30px}.progress-pie-120 .progress-info>small{line-height:18px}.progress-pie-50 .progress-info{padding-top:4px}.progress-pie-50 .progress-info>strong{font-size:20px;font-weight:400}.progress-pie-50 .progress-info>strong>small{font-size:14px}.progress-pie[data-value="100"] .progress-info>strong{-webkit-transform:scale(.7);-ms-transform:scale(.7);-o-transform:scale(.7);transform:scale(.7)}.progress-pie-24 .progress-info{right:-10px;left:-10px;width:auto;padding-top:0;font-size:12px;line-height:24px;-webkit-transform:scale(.9,1);-ms-transform:scale(.9,1);-o-transform:scale(.9,1);transform:scale(.9,1)}.progress-pie-24[data-value="100"] .progress-info{-webkit-transform:scale(.8,1);-ms-transform:scale(.8,1);-o-transform:scale(.8,1);transform:scale(.8,1)}.progress-pie-26 .progress-info{right:-10px;left:-10px;width:auto;padding-top:0;font-size:12px;line-height:26px;-webkit-transform:scale(.9,1);-ms-transform:scale(.9,1);-o-transform:scale(.9,1);transform:scale(.9,1)}.progress-pie-26[data-value="100"] .progress-info{-webkit-transform:scale(.8,1);-ms-transform:scale(.8,1);-o-transform:scale(.8,1);transform:scale(.8,1)}.status-bars{display:table;width:100%;height:140px;padding:5px;padding-top:50px;margin:0;overflow:hidden}.status-bars>li{position:relative;display:table-cell;text-align:center;vertical-align:bottom}.status-bars .bar{position:absolute;bottom:20px;left:50%;display:block;width:10px;margin-left:-5px;background:#2e7fff;border-radius:5px 5px 0 0}.status-bars .bar:after{position:absolute;right:-50px;bottom:0;left:-50px;display:block;height:1px;content:' ';background:#eee}.status-bars .title{font-size:12px;font-weight:400;color:#a6a8b6}.status-bars .value{position:relative;top:-20px;left:-20px;display:inline-block;width:50px;font-size:16px;font-weight:700;text-align:center}.status-bars-h{display:block;padding-right:50px;padding-left:60px;list-style:none}.status-bars-h>li{position:relative;height:40px;border-left:1px solid #eee}.status-bars-h .bar{position:relative;top:15px;display:block;height:10px;line-height:20px;background:#2e7fff;border-radius:0 5px 5px 0}.status-bars-h .title{position:absolute;top:-5px;left:-60px;width:60px;padding-right:10px;font-size:12px;color:#a6a8b6;text-align:right}.status-bars-h .value{position:absolute;top:-5px;right:-50px;display:block;width:40px;font-size:14px;font-weight:700;text-align:left;white-space:nowrap}.ring,[class*=" ring-"],[class^=ring-]{width:24px;height:24px;font-size:12px;line-height:24px;text-align:center;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAACZAAAAAwCAMAAACyyT3hAAAAP1BMVEUAAAAAAAA5rm06sG87snE4q200o2o7sG86r244q2w6r244rWw7snA7sG86rG05rm07snA5qWw7sW86sG48s3ECZypFAAAAFHRSTlMACl2b8TkPtnsqj0fVv4Zt4RrIqxUkbyYAAA1gSURBVHja7NrbbtswEIRhmyJ1IiVS0v/+z9rCbeGkdZodNEnVdHmTxP4wmF1exLB9eXCu12tqLX39cTGcm78use2z5Msx19wLfunGpOZvSv4xh/798tt+dKPSZ+jGMNn9/pWnXp1X7f9e+ynf53Xv3r37j/bLyfq4/1u+n8T8VI8jLWY/hbEbSjT76+2ssH77zeyBJHooom9dkHzssuQ5wvau+YuWz141/87zMoTewP9kP+7d2/31ZPln82e7L/fuP9LnJPk+DDDb/TQvEaDc/1//vk9Yvrl7vmHa+ztk347BXwcA1UMRPXCIfq+b5Jm3d82PneY5guSHG1fmVfu/837m5N69e/fu3T89fRZ9bRTBpyMCLFa/zfG7CU98l1/M/+EBS/59PT//bfD9lCS/pbAeR7bnb1OqC7AI+QWAkiz5Kdd1B6BVLb8Fk891Poox/7bPUNcIwGHrM3YDAHu1zyv0yWHcjfNen++nWvsvANC5d6/468nyz+bPdl/u3es+NFbFjw1A8BGAPUv5xGFMz/382HcAsNy+W9Sb9/PwwbP4ra5J8WluQDb7qQ4Ayybl05nnrQVgSFafB4AyWfvPEaBT57X2Seu3ecX9HBfN75t79+7du/+sPnVBy++A1e6nHaAEsz9ufk7W/gUgrvmBP/pf/bYDtDlp+/x8j4du1XwBFsHvQMlW348NIJjz0yH5bbz1n7R5m71/bUAL2n5iMueHAYjZvXv37t1/Tp8bJMFPDYhCfgTa2Ft9aEDJ5vzUbvnbC3562GcI5j6vf3T43/h+hlnJnwFGs99WgGDPv/lq9yPQsjCv1mcT/aUDGEVf3bt37979v+DH1kk+RIiCrwCz0AeI82bvc8vvzT5HYP1NfnjQv3uL/d+f/X/8ljUfGiD4ChDsPkcgCX0iUMX8UfDDw/6Xl+fV+q8Awb373/rLyfLP5s92X+4/rQ8QFV+BmO0+AyTND0nqT1H6xFf715/zWe35l5fXf3/e/YtnGugUnxsQhD47sNl9Klp+ipq/DmL/ovXPAJN79+7du/9on5dV80An+BEoye4nYLhofu/tPgAtafO26RWfn/chv9F93YX7N/PTDoOSHzW/LVA2Ib9Am+y+34Es5DdYlHmB2Lt37969+4/1fYQs+KlB6e0+Acsm9CnQXTQfJ61PTMI+dyiv99me7pO3u6+7cf9mvj/Ikgc6xRfNXyIcgt8KLIoX+0/A4d69e/fu/8znoPkOBsH3O8Qk5C8wiH12xR9AFvwAUfEzREOf/Wmf6Q3v967cv52fNJ+BIPgg+gmogk/xmb9a+lex/+ze/WN/PVn+2fzZ7sv93/MVVsUHIAn+AKrgV4hK/gxsYv+q7Ycg+AxMFt/d+9vy/QXZP+RHCIrvoGyCr9B6wa/QNq3/ftHyye7df2HP3pbQBKIgihqGmzKA4P7/b02lTOX2EncSEky1z8uungPosYyPj/8Nv8Mk/HaHm/Adzk/AIvy1hUb4vkKR5y0yf33Nd+K89vo+Xfy/81Oj/FahmPwBRuH7CjeTX6GRfUp8fHx8/Fc/UpRvoBpf4L4ZL/MfsLvzUoxfgM6e1/V5vNjn9jl/eLV/FrL/2DcwS1+lZxN+9X3a+Pj4+Pgvvm9B5Q8wyvxV+K11+VdgMv2r810LRZ63MX1m6IwX+ep+eMr49/HL3ChfoTF+eD65r/q+wiTy+zus8fHf+8vJ8s/mz3a94n/im075FarxE7Sby2975+8mf5T9G9gvLp/O9Jf5nfHNcz6vzzMLWfyX1wiz8Ss8ZP5g/A32+Pj4+P/XP0D5GRbjC9xkfjF+l/lV9h9gtP2PnOfN9JntfMQPsCeN/3/9dmc2/gqtym+d74BrfHx8/P/q+xaM34CryZf+CnTCT9Ca/A7YnG+N71V/P89La/r7eZ5pIYj/x35aNuUH6IzfpZ+hiY+Pj38X35dF+Ql241eoxncwGD/K/EbmL8r7/En2X+X8J99/dveb+4L/EB//zX+Wo/EFVunH+Pj4+HfxI0zG3+BmfIHF+AaKzB/VeU+Wv8BwZJ/14P56IbvEx39+bdDIBa4YP8bHx8e/kS/QGb/DZPwDVuMXGGV+Y3yR+QM+X/py5HlvB/e3C1l8/Fe/TYZfJtjNGxoY4uN/8JeT5Z/Nn+16vbVf7kX5Ga7GV+iM32Ey/gHNkfnDyfJnWA+d58H9L+aB/4Tj43/Vb4DxHdT4+Pj4f+WhVb7CZnwrfYWr8Tt0xt9lfj1ZfoVJedvn4P5ZyOL/nn+wGN9DGx8fH/+vPKB8630f/0f9drI+ymchiz+vB5Q/4Qd6fHz8efxCycIU/5G9e9lRHAaAKEr7kZi8nIT7/986UlatWaWGCbjpYn1UKizAQU7sZr0vyOzb9QkUv8Ek+qT4u77ksYv5q5g/tZQv91F9g0ta9j/Loy8RZnv7795LlvYf4b/U/JgWccLur/xBH2EXfbw4v28pP8B8cZ9HgzfR27/Tj9utpZvK7e2f8W09tWdv/8S+aBmi4OWnPis8FF+gKD7o+VHM78X8QfGD3uch+qG1bQ7s3+sDbIqPUEVf7O2/+5a2vWhqHyx7+++vUbwAGsQJfhEnjAhB8UXPLy3l97BcOgF7Y2H7v3yBeuVGqcXe/oW+qaN77O2f8jF2it9hVPwE9eKjpYqYH0S/XJk/ieM5i+P5gLG1o3Ls/7PfllE9Gkj0u3r0jb39q3xbE6q9/Qs9pE7wd+BKn4FNyU8wCn4DsuKT5vPF49Phw+k/389Mm+IT3DWf5MOh7e1f5G/n1ywPaW//MR4eil9EP0AU83sxf1d8Ub3YJ4q+iONZYRDHZ7b/aX6GovgIVfG9kr8ffYbz+fb2z/nbTZnw7O2f8V9N9clV8hMEwW8JRsF3q5i/QlX676rX+mSgqPmKnyGr42n/dh/mRfEjTIoP0Kv53Vlfj/z1tA/29s/5039hDmdv/2s9pE7wBXolv+r5aRP8CIh+UvpHSFnwQXy/GeabmG//fj9DUPyk+Q0IYp+q+ATB3v4lvqkJz96+ZR+pip8gCH5bIUr5ot/hofgIRfAZWK58vzMUxfcw2F/hQ1D8ANMm+Aq9mt8JPsB0Mj8e+UIfe/vn/Olv5KHs7e3P+QC74gdY75onC34BRsFXSEqfCFOn+XUT/KDnk+0v8AGK4LcJouC7FQYlf4VF6b9DEb4vmfP97X+HX859fvT843Vq880D2dv/o7811udyv0AV/IjmK/AQfE6wKB4YxD5B8AGoYp9R8AsQ7f+0a287bsJQFIapTxxssA3/+z9rJVS1VVOmezeDOp1s7mI+LS1bxJZChH7WeAdkpXcKX4Gs8yHqPFXyfC4/jspNlG/+VbyTPD/6/G+X9EAyb9681Dun8SMcCh8D7Enu0wirpk/T+QyMGh90fgiwKPvs5oU+45PCH4DGeyjK/KDpv0Lpyv7z237mp+/jDmRBvvnX8YLn5y/yxd/gU5g3b/4enyF0uU8FWpT7vgIKf56JSe5jgNY1XtlnV+5XQOjmhf6AfVb4AkGR3wuMXe7nBrsiPwN7evf+3xN7AAR9zJt/yst3uPO+efPmb/LbmhXeAwofCzAp8kcgyn0qyj4NcHLflfkzr+494yz3DmhZ7mdgH3T5JSp91/lxeL/+GWC++HyZb/6lfL72+Yl80d8OztvmzYv8I/9gff5/H6DKfQ6ofARwCj+i61MAp+sfstw7gPrZfO4KDzQn9w5A4TMQZrmvQFDkT0DIuv5j1Hku87eH1aiC9TH/Yn678tsT+Vdn5uM98+bNfxDvwyT3KUBwcr/o9oe+BJVPZ76T529AyBf+8jz9bH6BJYn9BLDI8x3A1nX5VZ6fGzAp+yxJ6ZX9t+ufn6m/W5+3+pt/Oe+vnx99/tWxYOM2buOfanyGluU+AHuX+jQ1IERxfi5Ac1Lfz/wmzk9uBML8P/jNO7lfgSL3sQC73DuANiWpzwA+i/Pns//UxflNN1/XgFaTuE8D1sv8/OADMLrLfPPmn/JXO/qX3w+aN2/+P/X1iHK/AZM43xWAsQt9XE5/zEKftwBQoji/nfnpH/m41S72GQg+Cn1aAcZpluYvqPLjCFCqdL59B1jFfvAAYZH2j8r5dq+b77Bd+70/8nQAtOX3+ebNP+Wvd9zz+vWjefNaP3ywPuZFfqNluQcIm9DnndNPIh+nMQDQJL5HN/kVAJY/+TinJPX5OCYXhX4F1hrnPgh8LAAUaX5tnH6rWdTfNXX+yZeaRd4DELaaZetZdwBGf/N8Jyfz+Wp9/Buv8mF97G/e/JP+egt6vAbz5s2/is9J4QthEvsVIBxR5F2A0y9d4Avwzfv8Jw8g9gXk/gDkvtdd5xfuzfdB5d2Iyg9uvHe+PrzD+izxyueLfPPmn/OaLXoYzJv/az98sD7m39XHrPAe1pqEfgUI6zJL/AYAsNc/94mA3O+g8M43lc8+aPxcR+7M73VH5aei7cOd8011fW59Vve2f1wf8+bv84+vmsybN2/+Xfzc5b4fR43i/DWU0U9uFvlpb0HsU578WOR+iJNfFb4r85Nbjjvz58kfe1N4p+wf3Xbsyvmq+9+4Ps68+dv8V5yOdlOUBd3vAAAAAElFTkSuQmCC);background-position:0 0;-webkit-background-size:1224px 24px;background-size:1224px 24px}.ring>span,[class*=" ring-"]>span,[class^=ring-]>span{display:inline-block;-webkit-transform:scale(.9,1);-ms-transform:scale(.9,1);-o-transform:scale(.9,1);transform:scale(.9,1)}.messager{border-radius:4px;-webkit-box-shadow:0 4px 16px rgba(0,0,0,.2),0 2px 8px rgba(0,0,0,.1);box-shadow:0 4px 16px rgba(0,0,0,.2),0 2px 8px rgba(0,0,0,.1)}.messager-icon{vertical-align:middle}.messager-icon>.icon{font-size:24px}.messager-content{padding:18px 20px;font-size:18px;line-height:30px}.messager-content>.icon{font-size:28px;line-height:30px}.messager-actions{vertical-align:middle}.messagger-zt{color:#313c52;background-color:#fff!important}.messagger-zt .messager-icon>.icon{color:#2e7fff}.messagger-zt .messager-actions>.action{color:#838a9d}.messagger-zt.messager-success .messager-icon>.icon{color:#22c98d}.messagger-zt.messager-danger .messager-icon>.icon{color:#fc5959}.messagger-zt.messager-warning .messager-icon>.icon{color:#ff9f46}.messagger-zt.messager-info .messager-icon>.icon{color:#37b2fe}.tree{padding-left:0;overflow:hidden}.tree ul{position:relative;display:none;padding-left:0}.tree li{position:relative;padding:2px 0 2px 15px;list-style:none}.tree li.heading{padding-left:5px;color:#313c52}.tree li>a{display:block;max-width:90%;padding:2px 6px;color:#313c52;word-break:break-all}.tree li>a:hover{color:#313c52}.tree li>a.tree-toggle:hover{background:0 0}.tree li.active>a{position:relative;font-weight:700;color:#2e7fff}.tree li>.list-toggle{position:absolute;top:1px;left:1px;z-index:10;width:20px;font-size:14px;line-height:22px;color:#cbd0db;text-align:center;cursor:pointer;-webkit-transition:all .2s;-o-transition:all .2s;transition:all .2s}.tree li>.list-toggle:before{content:"\f0da"}.tree li>.list-toggle:active,.tree li>.list-toggle:hover{color:#2e7fff}.tree li.has-active-item>.list-toggle{color:#313c52}.tree li.has-list.open>ul{display:block}.tree li.has-list.open>.list-toggle{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.tree li.has-list.open:before{position:absolute;top:16px;bottom:-5px;left:10px;display:block;content:' ';border-left:1px solid #d8d8d8}.tree-actions{display:inline-block;margin-left:5px;vertical-align:middle}.tree-actions a{display:inline-block;margin-left:5px;font-size:13px;opacity:.6}.tree-actions a:hover{opacity:1}.tree li>.module-name{color:#313c52;vertical-align:middle}.tree li>.module-name:hover{background-color:#f0f2f5}.tree li>.module-name:hover>a{color:#313c52}.treemap-node-fold-icon:before{position:relative;left:-4px;min-width:18px}.dropdown-menu{padding:5px 0;border-color:rgba(0,0,0,.1)}.dropdown-menu>li{padding:0 10px}.dropdown-menu>li>a{padding:2px 10px;margin:5px 0;border-radius:3px}.dropdown-menu>li>a>.icon{position:relative;left:-5px;opacity:.5}.dropdown-menu>li>a:hover>.icon{opacity:.8}.dropdown-menu>li.active>a,.dropdown-menu>li.selected>a{position:relative;color:#fff;background-color:#18a6fd}.dropdown-menu>li.selected>a:after{position:absolute;top:2px;right:4px;display:block;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;line-height:20px;text-transform:none;content:"\e5ca";speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.dropdown-menu>li.divider{margin:10px}.dropdown-submenu>a:after{margin-right:-5px}.dropdown-submenu>.dropdown-menu.pull-left{margin-left:-1px}.dropdown-submenu:focus>a,.dropdown-submenu:hover>a{color:#313c52;background-color:#e6f0ff}.dropdown-submenu:hover>a:after{border-left-color:#2e7fff}.dropdown-submenu>a:hover:after{border-left-color:#fff}.pager .btn{padding:3px 10px}.pager .btn .caret{opacity:.7}.pager>li>.pager-label{padding:2px;line-height:20px}.pager>li>.pager-item{min-width:20px;padding:1px;margin:2px 0;font-size:16px;line-height:20px;text-align:center;background:0 0;border-color:transparent}.pager>li>.pager-item:hover{background-color:rgba(0,0,0,.1)}.pager>li>.pager-item>.icon{position:relative;top:-1px}.pager>li>.btn:hover,.pager>li>a:hover{background:rgba(0,0,0,.1)}.pager>li.disabled>a.pager-item{background:0 0;border-color:transparent;opacity:.5}.pager>li.active>a{background-color:#18a6fd}.pager>li .btn-group .btn{padding:1px;margin:1px 0;border-radius:4px}.pager .dropdown-menu{width:200px}.pager .dropdown-menu>li{float:left;width:33.333333%}.modal-dialog{width:900px;max-width:1360px;border:none;border-radius:0;-webkit-box-shadow:0 0 20px 0 rgba(0,0,0,.25);box-shadow:0 0 20px 0 rgba(0,0,0,.25)}.modal-dialog.modal-md{width:700px}.modal-dialog.modal-xs{width:400px}.modal-dialog.modal-sm{width:500px}.modal-dialog.modal-lg{width:1200px}.modal-dialog.modal-fullscreen{position:fixed;max-width:initial}.modal-header{padding:20px 0;margin:0 20px}.modal-header>.close{color:#838a9d;text-shadow:0 1px 0 rgba(255,255,255,.85);opacity:1}.modal-header>.close:hover{color:#0b0f18}.modal-footer{padding:20px 0;margin:0 20px}.modal-title{font-size:14px;font-weight:400;line-height:20px}.modal-actions{position:absolute;top:16px;right:16px}.modal-actions .divider{position:relative;top:5px;display:inline-block;width:0;height:20px;margin:0 10px;border-left:#eee 1px solid}.modal-actions>.dropdown{display:inline-block}.modal-body{padding:20px}.modal-iframe .modal-body>iframe{border-radius:0}.modal.fade .modal-dialog{-webkit-transform:translate(0,-3%);-ms-transform:translate(0,-3%);-o-transform:translate(0,-3%);transform:translate(0,-3%)}.modal.fade.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-simple .modal-footer{padding-top:0;border-top:none}.modal-iframe .modal-header{position:relative;z-index:10;min-height:0;padding:0;border:none}.modal-iframe .modal-title{display:none}.modal-iframe .modal-header .close{position:absolute;top:12px;right:10px;font-size:32px;font-weight:200}.modal-iframe .modal-dialog{overflow:hidden}.modal-inverse .modal-header>.close{color:rgba(255,255,255,.7);text-shadow:none}.modal-inverse .modal-header>.close:hover{color:#fff}.modal-scroll-inside>.modal-dialog{max-height:100%}.hide-modal-close .modal-iframe .modal-header .close{display:none}.tile{text-align:center}.tile-title{line-height:20px;color:#313c52}.tile-amount{font-size:32px;font-weight:700;line-height:56px}.timeline>li{position:relative;list-style:none}.timeline>li:before,.timeline>li>a:after,.timeline>li>div:after{position:absolute;left:-20px;display:block;width:15px;height:15px;content:' ';border-radius:50%}.timeline>li:before{top:12px;left:-16px;z-index:3;width:7px;height:7px;background-color:#c4c4c4;border:none;border:1px solid #c4c4c4}.timeline>li>a:after,.timeline>li>div:after{top:11px;left:-17px;z-index:3;width:9px;height:9px;background-color:#2e7fff;border-radius:50%;opacity:0}.timeline>li+li:after{position:absolute;top:-12px;bottom:20px;left:-13px;z-index:1;display:block;content:' ';border-left:1px solid #eee}.timeline>li.active>a:after,.timeline>li.active>div:after{opacity:1}.timeline>li.active:before{top:8px;left:-20px;width:15px;height:15px;background-color:rgba(46,127,255,.2);border:none}.timeline>li>a,.timeline>li>div{display:block;padding:5px;line-height:20px}.timeline>li.active>a{color:#313c52}.timeline-tag{position:absolute;top:5px;left:-115px;font-size:12px}.timeline-tag-left{padding-left:115px}.timeline-sm{font-size:12px}.timeline-sm>li:before,.timeline-sm>li>a:after,.timeline-sm>li>div:after{top:10px;left:-20px;width:11px;height:11px}.timeline-sm>li.active:before,.timeline-sm>li:before{top:10px;left:-18px;width:11px;height:11px;background:0 0;border:1px solid #eee}.timeline-sm>li>a,.timeline-sm>li>div{line-height:20px}.timeline-sm>li>a:after,.timeline-sm>li>div:after{top:13px;left:-15px;width:5px;height:5px}.form-control{-webkit-box-shadow:none;box-shadow:none}.form-horizontal .form-group>label{padding-right:0}.form-actions{margin-top:20px;margin-bottom:0}.form-actions .btn{margin-right:10px}form label{font-weight:400;color:#313c52}.form-group .btn+.btn{margin-left:5px}.table-form{margin-bottom:0;table-layout:fixed}.table-form>thead>tr>th.required:after{position:relative;top:3px;right:auto;left:4px;display:inline-block;vertical-align:middle}.table-form>tbody>tr>td,.table-form>tbody>tr>th,.table-form>tfoot>tr>td,.table-form>thead>tr>th{padding:7px;vertical-align:middle;border-bottom:none}.table-form>tfoot>tr>td{padding:20px 7px 10px}.table-form>tbody>tr>th{width:100px;font-weight:700;text-align:right}.table-form .input-group{width:100%}.chosen-container-single .chosen-single{position:relative}.chosen-container-single .chosen-single>span{height:20px;line-height:20px;word-break:break-all;white-space:normal}.chosen-container-single .chosen-single div b{position:relative;top:1px;color:#cbd0db;opacity:1}.chosen-container-single .chosen-search:before{top:8px;right:15px}.chosen-container-multi .chosen-choices li.search-choice{font-size:13px;background:#eee;border-color:#cbd0db;-webkit-box-shadow:none;box-shadow:none}.chosen-container-single .chosen-search input[type=text]{height:30px;padding:3px 25px 3px 5px}.chosen-container-single .chosen-search{padding:3px 10px 0}.chosen-container-single .chosen-single{overflow:visible}.chosen-container .chosen-results{max-height:245px;padding:10px}.chosen-container .chosen-results>li{border-radius:4px}.chosen-container .chosen-results li.highlighted em{color:#fff}.table-responsive .chosen-container .chosen-results{max-height:200px}.picker>.picker-selections>.picker-selection>.picker-selection-text{word-break:break-word}.chosen-container .chosen-drop.chosen-no-wrap>.chosen-results>li,.picker-drop-menu>.picker-option-list>.picker-option .picker-option-text{text-overflow:clip;white-space:nowrap!important}.picker-drop-menu>.picker-option-list>.picker-option .picker-option-text{display:block;overflow:hidden;word-break:break-word}.chosen-compact.chosen-container-single .chosen-single>.chosen-search{top:-2px;right:-1px;bottom:-1px;left:-1px;display:none;height:auto;padding:0;opacity:0}.chosen-compact.chosen-container-single .chosen-single>.chosen-search>input{height:31px;padding:5px 26px 5px 8px;font-size:inherit;line-height:20px}.chosen-compact.chosen-container-single .chosen-single>.chosen-search:before{right:8px}.datetimepicker{padding:10px}.datetimepicker td.day.today{background-color:#fc7272}.datetimepicker td.day.active{background-color:#18a6fd}.datetimepicker tfoot th,.datetimepicker thead th{color:#838a9d}.input-control .colorpicker{top:0;z-index:auto;opacity:1}.input-control .colorpicker .btn{padding:5px}.input-control .input-control-icon-right.btn{top:0}.colorpicker .dropdown-menu{min-width:232px;padding:5px 10px 10px 10px}.colorpicker .dropdown-menu>li{display:block;float:left;padding:5px}.colorpicker .dropdown-menu>li.heading{width:100%;margin-bottom:5px;font-size:16px;font-weight:700;text-align:left}.colorpicker .dropdown-menu>li.heading>.icon-close{position:relative;top:4px;float:right;cursor:pointer;opacity:.6}.colorpicker .dropdown-menu>li>a{position:relative;display:block;width:100%;height:100%;padding:0;margin:0;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-align:center;text-transform:none;border:1px solid transparent;border-radius:50%;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.colorpicker .dropdown-menu>li>a:before{position:absolute;top:0;display:block;width:100%;height:20px;line-height:18px}.colorpicker .dropdown-menu>li>a:hover{-webkit-box-shadow:0 1px 4px rgba(0,0,0,.25);box-shadow:0 1px 4px rgba(0,0,0,.25)}.colorpicker .dropdown-menu>li>a.active:before{font-size:14px;content:"\e5ca"}.colorpicker .dropdown-menu>li>a.empty{color:#666;background:#fff}.colorpicker .dropdown-menu>li>a.empty:before{content:"\e90d"}.colorpicker .btn{position:relative}.colorpicker .btn .color-bar{position:absolute;right:5px;bottom:3px;left:5px;height:3px}.colorpicker .btn .color-bar[style*='background: ']+.ic{position:relative;top:-2px}.colorpicker .btn .ic{color:#cbd0db}.colorpicker .btn:hover .ic{color:#838a9d}.input-group .colorpicker{z-index:3}.input-group .chosen-container{display:table-cell}.input-group-addon{border-right-width:0;border-left-width:0}.input-group-addon:first-child{border-left-width:1px}.input-group-addon:last-child{border-right-width:1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin:0}.input-group-cell{display:table-cell;width:1%;padding:0 12px;white-space:nowrap;vertical-align:middle}.ke-container{border-color:#dcdcdc!important;border-radius:2px!important}.ke-container.focus{border-color:#2e7fff!important}.ke-toolbar{border-color:#dcdcdc!important}.required:after{top:6px;right:-10px;font-size:20px}td.required:after{top:12px;right:-5px}.input-group>.chosen-container.required:after,.input-group>.input-control.required:after{top:1px;right:1px;z-index:2}.input-group.required .required:after{display:none}.file-input{position:relative;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.file-input .input-group{width:auto}.file-input .input-group>.input-group-cell:first-child{padding-right:0;padding-left:7px}.file-input input[type=file]{position:absolute;width:0;height:0;opacity:0}.file-input .file-title{display:inline-block;max-width:400px;overflow:hidden;text-overflow:unset;white-space:nowrap;vertical-align:middle}.file-input .file-editbox{min-width:200px;max-width:100%}.file-input .file-size{display:inline-block;vertical-align:middle}.edit .file-input-empty,.file-input-edit,.file-input-normal,.normal .file-input-empty{display:none}.edit .file-input-edit,.normal .file-input-normal{display:block}.edit .file-input-edit.input-group,.normal .file-input-normal.input-group{display:table}.edit .file-input-normal{display:none!important}.file-input-normal>.input-group-btn{width:auto}.input-group .chosen-container-active .chosen-choices{border-color:#2e7fff!important}.input-group .chosen-container{min-width:100px}.input-group .input-group-btn .btn>.icon{line-height:17px}.os-mac select.form-control{-webkit-appearance:none;background-image:url(data:image/gif;base64,R0lGODlhCQAFAIAAAMvQ2////yH5BAEAAAEALAAAAAAJAAUAAAIKhH+BGYoNGWxgFgA7);background-image:url(data:image/gif;base64,R0lGODlhBwAEAIAAAMvQ2////yH5BAEAAAEALAAAAAAHAAQAAAIIhA+BGWoNWSgAOw==);background-repeat:no-repeat;background-position:right 5px top 12px;-moz-appearance:none}input::-webkit-contacts-auto-fill-button{position:absolute;right:0;display:none!important;pointer-events:none;visibility:hidden}.chosen-choices.has-error,.chosen-single.has-error,.form-control.has-error{border-color:#fc5959!important;-webkit-box-shadow:0 0 6px #febdbd!important;box-shadow:0 0 6px #febdbd!important}.popover-success.popover-form-result{font-weight:700;color:#fff;background:#22c98d}.popover-success.popover-form-result.popover.right .arrow:after{border-right-color:#22c98d}textarea.kindeditor{padding:0!important}.form-unsaved{outline:2px solid #ff9f46;-webkit-box-shadow:0 1px 12px #ff9f46;box-shadow:0 1px 12px #ff9f46;-webkit-transition:all .5s;-o-transition:all .5s;transition:all .5s}select[multiple].picker-select,select[multiple][data-toggle=picker]{max-height:32px;visibility:hidden}.picker-selection-remove:before{font-size:18px}.picker-user-option{position:relative;padding-left:40px}.picker-user-option>.avatar{position:absolute;top:5px;left:10px}.picker-user-option>.picker-option-text{display:inline-block;min-width:40px;font-weight:700}.picker-user-option>.dept-name,.picker-user-option>.role-name{display:inline-block;margin-left:10px;font-size:12px;opacity:.6}.picker-user-option>.dept-name{min-width:50px}#mainHeader{height:50px;color:#fff;background:#3785ff;border-top-color:#2e7fff;border-bottom-color:#e6f0ff}#mainHeader>.container{min-width:1200px;padding:0}#heading{position:absolute;top:10px;left:20px}@media (min-width:1400px){#heading{left:40px}}#heading h1{float:left;max-width:250px;margin:0;overflow:hidden;font-size:20px;font-weight:400;line-height:30px;text-overflow:unset;white-space:nowrap}#heading h1 a{color:inherit;text-decoration:inherit}#heading h1.long-name{position:relative;top:-5px;display:table-cell;font-size:16px;line-height:20px;word-break:break-all;white-space:normal}#heading>.btn{display:block;float:left;height:20px;padding:1px 5px;margin:0;margin:5px 0 0 10px;font-size:12px;font-weight:lighter;line-height:18px;background-color:rgba(255,255,255,.2);border:none}#heading>.btn:hover{background-color:rgba(0,0,0,.1)}#navbar{margin:0 auto;text-align:center}#navbar .nav{display:inline-block}#navbar .nav>li>a{padding:10px;line-height:30px;color:#fff;border-radius:0;opacity:.9}@media (max-width:1400px){#navbar .nav>li>a{padding:10px 8px}}#navbar .nav>li>a:focus,#navbar .nav>li>a:hover{background:rgba(0,0,0,.15);opacity:1}#navbar .nav>li.active>a{font-weight:700;background:rgba(0,0,0,.1);opacity:1}#navbar .nav>li.divider{display:block;width:2px;height:20px;margin:15px 8px;background:rgba(255,255,255,.4)}@media (max-width:1400px){#navbar .nav>li.divider{margin:15px 5px}}@media (max-width:1300px){#navbar .nav>li.divider{margin:15px 3px}}#navbar .nav>li.divider:last-child{display:none}#navbar .nav .dropdown-menu li>a{text-align:left}#toolbar{position:absolute;top:12px;right:20px;font-size:12px;color:#fff}@media (min-width:1400px){#toolbar{right:40px}}#extraNav{text-align:right}#extraNav>li{display:inline-block;float:none;text-align:left}#extraNav>li>a{display:block;padding:0;color:#fff;opacity:.75}#extraNav>li>a:hover{text-decoration:unset;background-color:rgba(0,0,0,.1);opacity:1}#extraNav>li.open>a{background-color:rgba(0,0,0,.1)}#extraNav>li+li{margin-left:10px}#showSearchGo{color:#fff;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.5)}#searchbox{position:relative;float:left;width:150px}#searchbox .input-group-btn .btn{position:relative;padding:1px 4px;font-size:12px;line-height:20px;color:#fff;background-color:rgba(255,255,255,.15);border-right:none;border-radius:2px}#searchbox .input-group-btn .btn:after{position:absolute;top:3px;right:0;bottom:3px;display:block;width:1px;content:' ';background-color:rgba(255,255,255,.15)}#searchbox .input-group-btn .btn:hover{background-color:rgba(255,255,255,.25)}#searchGo{position:absolute;top:0;right:-1px;z-index:9;min-width:24px;height:24px;padding:2px 3px;font-size:12px;line-height:20px;color:#fff;background-color:#18a6fd;border-radius:2px}#searchGo:hover{color:#fff!important;background-color:#2e7fff}#searchInput{height:24px;padding:2px 30px 2px 5px;color:#fff;text-align:left;background:rgba(255,255,255,.15);border-color:transparent;border-radius:0 12px 12px 0;-webkit-transition:background .2s,border .2s;-o-transition:background .2s,border .2s;transition:background .2s,border .2s}#searchInput:hover{background:rgba(255,255,255,.25)}#searchInput:focus{color:#333;background:#fff}#searchInput::-webkit-input-placeholder{font-size:12px;color:#fff;color:rgba(255,255,255,.5)}#searchInput::-moz-placeholder{font-size:12px;color:#fff;color:rgba(255,255,255,.5)}#searchInput:-ms-input-placeholder{font-size:12px;color:#fff;color:rgba(255,255,255,.5)}#searchInput::placeholder{font-size:12px;color:#fff;color:rgba(255,255,255,.5)}#searchInput:focus::-webkit-input-placeholder{color:#838a9d}#searchInput:focus::-moz-placeholder{color:#838a9d}#searchInput:focus:-ms-input-placeholder{color:#838a9d}#searchInput:focus::placeholder{color:#838a9d}#searchTypeMenu{min-width:220px}#searchTypeMenu>li{float:left;width:50%}#searchTypeMenu>li>a{margin:4px 0}#userNav .avatar{display:inline-block;vertical-align:middle}#userNav>li>a{padding:2px 6px;line-height:20px;color:#fff;opacity:.9}#userNav>li>a .user-name{max-width:100px;overflow:hidden;font-size:15px;text-overflow:unset;white-space:nowrap}#userNav>li>a:hover{background-color:rgba(0,0,0,.1);opacity:1}#userNav>li>a:hover>i{opacity:1}#userNav>li>a span{vertical-align:middle}#userNav>li.open>a{background-color:rgba(0,0,0,.1)}#userNav>li.has-new-items>a{position:relative}#userNav>li.has-new-items>a:before{position:absolute;top:3px;right:-1px;display:block;width:4px;height:4px;content:' ';background-color:#fc5959;border-radius:50%}#userNav .dropdown-menu{min-width:150px}#userNav .dropdown-menu>li>a>.icon{position:absolute;top:10px;right:5px;display:block;width:20px;height:20px;line-height:20px;text-align:center}#userNav .user-profile-item>a{position:relative;padding-left:45px}#userNav .user-profile-item .avatar{position:absolute;top:6px;left:5px}#userNav .user-profile-name{font-size:16px}#userNav .user-profile-role{font-size:12px;color:#a9abb8}#userNav .no-role .user-profile-role{display:none}#userNav .no-role .user-profile-name{line-height:40px}#subHeader{min-height:50px;background:#fff}#subHeader>.container{padding:0 20px}@media (min-width:1400px){#subHeader>.container{padding:0 40px}}#pageNav{position:absolute;top:8px;left:0;left:20px}@media (min-width:1400px){#pageNav{left:40px}}#subNavbar{margin-top:5px;font-size:13px;text-align:center}#subNavbar .nav{display:inline-block}#subNavbar .nav>li>a{padding:8px 12px;line-height:24px;color:#313c52}#subNavbar .nav>li>a:hover{color:#313c52;background-color:rgba(0,0,0,.075)}#subNavbar .nav>li.active>a{font-weight:700;color:#2e7fff}#subNavbar .nav>li.divider{display:block;width:2px;height:20px;margin:9px 5px;background-color:rgba(0,0,0,.05)}#subNavbar .dropdown-menu{text-align:left}[lang=en] #subNavbar>.nav>li>a{padding-right:8px;padding-left:8px}#pageActions{position:absolute;top:9px;right:20px}@media (min-width:1400px){#pageActions{right:40px}}.cell{padding:10px;background-color:#fff;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045);box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045)}.cell+.cell{margin-top:10px}.cell>.panel{margin:0;-webkit-box-shadow:none;box-shadow:none}.cell>.panel>.panel-heading{padding:5px 5px 10px}.cell>.panel>.panel-heading .panel-actions{padding:0}.cell>.panel>.panel-body{padding:5px}.cell>.table{margin:0}#main{min-width:1200px;padding:20px 0}#main>.container{padding:0 20px}@media (min-width:1400px){#main>.container{padding:0 40px}}#header,#header+#main{min-width:1200px}#mainMenu{margin:-10px 0 8px}.main-content{padding:20px;background-color:#fff;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045);box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045)}@media (min-width:1400px){.main-content>.center-block{max-width:1350px;padding:20px;border:1px solid #eee}.main-content>.center-block .main-header{background-color:#f1f1f1}}.main-content>h2{margin:0 0 20px}.main-content .cell{-webkit-box-shadow:none;box-shadow:none}.main-header{padding:5px 20px;border-bottom:1px solid #eee}.main-header:after,.main-header:before{display:table;content:" "}.main-header:after{clear:both}.main-header>h2{display:block;float:left;margin:0 10px 0 0;font-size:14px;line-height:34px}.main-header>h2 .label-id{margin-right:5px}.main-header>h2 small{font-size:14px;font-weight:400}.main-content .main-header{margin:-20px -20px 10px}.main-header .label{top:-1px}.main-row{display:table;width:100%;table-layout:fixed}.main-row>[class*=" col-"],.main-row>[class*="-col"],.main-row>[class^=col-]{display:table-cell;vertical-align:top}@media (max-width:720px){.main-row{display:block}.main-row>*{display:block;width:100%}.main-row .side-col{width:100%;padding:0}.main-row .main-col+.side-col,.main-row .side-col+.main-col{margin-top:10px}}.main-row.hide-side .side-col{display:none}.main-form{margin:0}@media (min-width:720px){.main-content>.center-block .main-form{padding-right:20px}}#main .side-col .tabs{padding:5px}#main .side-col .nav-tabs{margin:0 5px 5px 5px}#main .side-col .nav-tabs>li{margin:0}#main .side-col .nav-tabs>li+li{margin-left:10px}#main .side-col .nav-tabs>li>a{padding:8px 5px}#main .side-col .tab-content .tab-pane table{border:none}.main-actions .btn-toolbar{display:inline-block;padding:4px 15px;color:#fff;pointer-events:auto;background:#717171;background-color:rgba(90,90,90,.85);border-radius:4px}.main-actions .btn-toolbar .divider{margin-right:15px;margin-left:15px;border-color:rgba(255,255,255,.1)}.main-actions .btn-toolbar .btn{height:30px;padding-right:10px;padding-left:10px;margin-right:0;color:#fff;background-color:transparent;border:none}.main-actions .btn-toolbar .btn+.btn{margin-left:10px}.main-actions .btn-toolbar .btn:focus,.main-actions .btn-toolbar .btn:hover{background-color:rgba(255,255,255,.2)}.main-actions .btn-toolbar .btn.btn-icon{min-width:32px;padding-right:0;padding-left:0}.main-actions .btn-toolbar .btn+.btn-group{margin-right:0;margin-left:10px}#mainContent .main-col>.main-actions{padding:30px 0 0 0;text-align:center}#mainContent .main-col>.main-actions>.btn-toolbar{visibility:visible;opacity:1;-webkit-transition:opacity .2s;-o-transition:opacity .2s;transition:opacity .2s}#mainActions{position:fixed;top:0;right:0;bottom:0;left:0;text-align:center;pointer-events:none}#mainActions .btn-toolbar{position:relative;top:-90px}#mainActions .dropdown-menu{text-align:left}#mainActions>.container{height:100%}.main-actions-holder{display:none}.main-actions-fixed .main-actions-holder{display:block}.main-actions-fixed #mainContent .main-col>.main-actions{position:fixed;bottom:10px}.main-actions-fixed.body-modal #mainContent .main-col>.main-actions{bottom:20px}#nextPage,#prevPage{position:absolute;top:50%;left:-10px;width:40px;height:60px;padding:10px 0;margin-top:-30px;line-height:40px;color:#fff;text-align:center;pointer-events:auto;background:#717171;background-color:rgba(90,90,90,.85);-webkit-box-shadow:0 2px 15px 2px rgba(0,0,0,.05);box-shadow:0 2px 15px 2px rgba(0,0,0,.05)}#nextPage:hover,#prevPage:hover{-webkit-box-shadow:0 2px 15px 2px rgba(0,0,0,.15);box-shadow:0 2px 15px 2px rgba(0,0,0,.15)}#nextPage>i,#prevPage>i{display:block;font-size:18px;line-height:36px}#nextPage{right:-10px;left:auto}@media (max-width:1800px){#prevPage{left:-3px}#nextPage{right:-3px}}#sidebarHeader{position:relative;float:left;width:180px;height:34px;padding-right:20px;margin-right:20px;background:#fff;border-left:4px solid #2e7fff;border-radius:4px 2px 2px 4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045);box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045)}#sidebarHeader:after{position:absolute;top:-1px;right:-8px;display:block;width:0;height:0;content:' ';border-color:transparent transparent transparent #fff;border-style:solid;border-width:18px 0 18px 8px}#sidebarHeader .title{padding:0 5px;overflow:hidden;font-size:14px;font-weight:700;line-height:32px;color:#2e7fff;text-align:center;text-overflow:unset;white-space:nowrap}#sidebarHeader .title>a{position:absolute;top:0;right:0;width:20px;opacity:.5}#sidebarHeader .title>a:hover{opacity:1}#sidebar{position:relative;-webkit-transition:width .2s,padding .2s;-o-transition:width .2s,padding .2s;transition:width .2s,padding .2s}#sidebar>.sidebar-toggle{position:absolute;top:0;right:5px;bottom:0;width:10px;cursor:pointer;background:0 0;border-radius:5px;-webkit-transition:background-color .2s,opacity .5s;-o-transition:background-color .2s,opacity .5s;transition:background-color .2s,opacity .5s}#sidebar>.sidebar-toggle>.icon{position:absolute;top:50%;left:-1px;width:12px;height:30px;margin-top:-10px;line-height:30px;color:#fff;text-align:center;background:#7dcdfe;border-radius:6px}#sidebar>.sidebar-toggle>.icon:before{position:relative;left:-1px}#sidebar>.sidebar-toggle:before{position:absolute;top:0;right:-5px;bottom:0;left:-5px;display:block;content:' '}#sidebar>.sidebar-toggle:hover{background:rgba(0,0,0,.075)}#sidebar>.cell{position:relative;left:0;width:180px;-webkit-transition:left .2s,opacity .2s;-o-transition:left .2s,opacity .2s;transition:left .2s,opacity .2s}#sidebar.no-animate>.cell{display:none;-webkit-transition:none;-o-transition:none;transition:none}.hide-sidebar #sidebar>.cell{position:absolute;left:-200px;visibility:hidden;opacity:0}.hide-sidebar #sidebar{position:relative;width:0;padding:0}.hide-sidebar #sidebar>.sidebar-toggle>.icon:before{content:"\e315"}@media (max-width:720px){#sidebar>.cell{width:100%}}#queryBox{max-height:0;padding:0;overflow:hidden;-webkit-transition:cubic-bezier(.175,.885,.32,1) .2s;-o-transition:cubic-bezier(.175,.885,.32,1) .2s;transition:cubic-bezier(.175,.885,.32,1) .2s;-webkit-transition-property:padding,max-height,margin;-o-transition-property:padding,max-height,margin;transition-property:padding,max-height,margin}#queryBox>form{visibility:hidden;-webkit-transition:visibility .2s .2s;-o-transition:visibility .2s .2s;transition:visibility .2s .2s}#queryBox.loading{height:50px}#queryBox.show{min-height:110px;max-height:300px;margin-bottom:10px;overflow:visible}#queryBox.show>form{visibility:visible}#queryBox.divider{border-bottom:1px solid #eee}#main .querybox-toggle.querybox-opened{position:relative;color:#2e7fff;background:0 0;border:none}#main .querybox-toggle.querybox-opened:before{position:absolute;bottom:-14px;left:50%;width:0;height:0;content:' ';border-color:transparent transparent #fff transparent;border-style:solid;border-width:0 10px 10px 10px}#contentNav{padding:5px;background:#fff;border-bottom:1px solid #eee}#contentNav .nav>li>a{position:relative;padding:6px 10px;color:#838a9d}#contentNav .nav>li.active>a{font-weight:700;color:#2e7fff}#contentNav .nav>li.active>a:before{position:absolute;right:10px;bottom:3px;left:10px;display:block;height:2px;content:' ';background:#2e7fff}.body-modal{padding-bottom:0}.body-modal #main,.body-modal .container{min-width:0!important}.body-modal #main{padding:0}.body-modal .main-header{position:fixed;top:0;right:20px;left:20px;z-index:100;padding:13px 48px 13px 0;margin:0;background:#fff}.body-modal #mainContent{padding-top:70px}.body-modal .main-header>h2{max-width:100%;overflow:hidden;font-size:14px;text-overflow:unset;white-space:nowrap}.body-modal .cell,.body-modal .main-content{-webkit-box-shadow:none;box-shadow:none}.body-modal #mainMenu{position:fixed;top:0;right:0;left:0;z-index:100;padding:12px 60px 12px 10px;margin:0;background:#fff}.body-modal #mainMenu>.btn-toolbar.pull-left.divider{display:none}.body-modal #mainMenu>.btn-toolbar{width:100%;margin-left:20px}.body-modal #mainMenu>.btn-toolbar>.divider:first-child{display:none}.body-modal #mainMenu>.btn-toolbar .page-title{width:100%;margin-left:0}.body-modal #mainMenu>.btn-toolbar .page-title>.text{position:relative;top:-2px;display:inline-block;max-width:85%;max-width:-webkit-calc(100% - 100px);max-width:calc(100% - 100px);overflow:hidden;text-overflow:unset;white-space:nowrap;vertical-align:middle}.body-modal #mainMenu+#mainContent.main-row{padding:60px 10px 0}.body-modal #mainMenu+#mainContent.main-row .cell{border:1px solid #efefef;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045);box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045)}.body-modal #mainActions{top:auto}.body-modal #mainActions .btn-toolbar{top:auto;bottom:10px}.body-modal.m-bug-view,.body-modal.m-story-view,.body-modal.m-task-view,.body-modal.m-testcase-view,.body-modal.m-testtask-view,.body-modal.m-todo-view{padding-bottom:20px;border-radius:3px}#tabsNav{position:relative}#tabsNav .tab-pane>.actions{position:absolute;top:-8px;right:0}#tabsNav .tab-pane>.cell,#tabsNav .tab-pane>.main-table{padding:0;border:1px solid #c4c4c4;border-top:none;border-radius:0 0 4px 4px}#tabsNav .tab-pane>.cell .detail-title{padding-left:5px}#helpContent{position:fixed;top:50px;right:0;bottom:40px;left:0;display:none;background-color:#fff}#helpContent .load-error{display:none;padding:20px}#helpContent .show-error .load-error{display:block}#mainMenu>.btn-toobar .btn-link .label-light,#mainMenu>.btn-toolBar .btn-link .label-light,#mainMenu>.btn-toolbar .btn-link .label-light{color:#313c52;background:#fff}#mainMenu>.btn-toobar .btn-active-text,#mainMenu>.btn-toolBar .btn-active-text,#mainMenu>.btn-toolbar .btn-active-text{color:#1e6aeb;background:#e6eaf1}#mainMenu>.btn-toobar .btn-active-text .text,#mainMenu>.btn-toolBar .btn-active-text .text,#mainMenu>.btn-toolbar .btn-active-text .text{top:0}#mainMenu>.btn-toobar .btn-active-text .text:after,#mainMenu>.btn-toolBar .btn-active-text .text:after,#mainMenu>.btn-toolbar .btn-active-text .text:after{border:none}#menu{background:#415a72}#userNav>li>a>.icon{color:#fff;filter:none;opacity:.25;-webkit-filter:none}.picker-option.picker-option-active,.picker-single .picker-option.picker-option-selected.picker-option-active{background:#2e7fff}select.form-control option.selected,select.form-control option:hover{background:#2e7fff}#guideDialog .project-type-img:hover,#guideDialog .project-type.active img,.kanbans .panel:hover{border-color:#2e7fff}#dashboard .block-welcome .progress{background:#edeef2}#dashboard .block-welcome .progress>.progress-bar{background:#3785ff}#assigntomeBlock .nav>li.active>a{color:#1e6aeb}.consumedTotally{color:#2e7fff;background:#e6f0ff}#toggleFoldIcon .icon-border{border:1px solid #d5e5ff}#createEffort .table tbody .date-group .input-group-addon .icon,#recordForm .table tbody .date-group .input-group-addon .icon,#toggleFoldIcon{color:#2e7fff}.text-middle td,.text-middle th{vertical-align:middle}.text-center td,.text-center th{text-align:center}.c-sm{width:40px}.c-id{width:90px}.c-id-sm{width:70px}.c-id-xs{width:55px}.c-date{width:100px}.c-num,.c-pri,.c-type{width:80px;overflow:hidden}.c-begin,.c-end,.c-time{width:65px}.c-hours{width:60px}.c-actions-1{width:50px}.c-actions-2{width:75px}.c-actions-3{width:102px}.c-actions,.c-actions-4{width:128px}.c-actions-5{width:155px}.c-actions-6{width:180px}.c-product,.c-project{width:180px}.c-plan{width:130px}.c-datetime{width:120px}.c-stage,.c-status,.c-user{width:80px}.c-side{width:200px;border-right:10px solid #efefef}.c-assign,.c-assignedTo,.c-openedBy{width:130px}.c-progress{width:155px}.c-assign,.c-assignedTo,.c-openedBy,.c-product,.c-project,.c-status,.c-url,.c-user{overflow:hidden;text-overflow:unset;white-space:nowrap}.assigned-none{color:#1e6aeb!important;background:inherit}.assigned-current{color:#f38f19!important;background:inherit}.assigned-other{color:#313c52!important;background:inherit}a.assigned-current:hover,a.assigned-none:hover,a.assigned-other:hover{color:#1e6aeb!important;background:#fff;border-color:#fff!important}a.assigned-current:hover .icon,a.assigned-none:hover .icon,a.assigned-other:hover .icon{color:#1e6aeb!important}td.c-name,td.c-title{overflow:hidden;text-align:left!important;text-overflow:unset;white-space:nowrap}td.c-actions{position:relative;padding-top:0;padding-bottom:0;overflow:hidden;text-overflow:unset;white-space:nowrap;vertical-align:middle}td.c-actions .btn-link{color:#313c52;background:0 0}td.c-actions .btn-link:hover{color:#2e7fff;background:#e6f0ff}td.c-actions .btn.disabled,td.c-actions .btn[disabled]{pointer-events:auto}td.c-actions .more{position:absolute;top:50%;right:100%;display:none;padding-right:4px;padding-left:20px;margin-top:-15px;margin-right:-6px;white-space:nowrap;background-color:#fafafa;-webkit-transition:opacity .3s,margin .3s;-o-transition:opacity .3s,margin .3s;transition:opacity .3s,margin .3s}tr:hover td.c-actions .more{display:block}td:hover+td.c-actions>.more{margin-right:-15px;pointer-events:none;opacity:.15}tr[data-url]{cursor:pointer}.table tbody>tr>td,.table thead>tr>th{vertical-align:middle}.table tbody>tr>td.has-btn,.table thead>tr>th.has-btn{padding-top:1px;padding-bottom:1px;overflow:visible}.table tbody>tr>td .progress,.table thead>tr>th .progress{height:6px}.table .em,.table em{color:#313c52}.table .divider{border-bottom:10px solid #efefef}.table .divider-top{border-top:10px solid #efefef}.table .btn-icon-left{max-width:100%;padding-left:20px;overflow:hidden;line-height:18px;text-align:left;text-overflow:unset;background:0 0;border-color:#eaf3fc}.table .btn-icon-left>.icon{width:20px;font-size:14px;background:0 0!important;opacity:0}.table .btn-icon-left.btn-sm{height:26px;font-size:13px}.table .btn-icon-left:active,.table .btn-icon-left:focus,.table .btn-icon-left:hover{border-color:rgba(0,0,0,.2)}.table .btn-icon-left:active>.icon,.table .btn-icon-left:focus>.icon,.table .btn-icon-left:hover>.icon{opacity:1}.table .btn-icon-left>.text{padding-left:25px}.table thead>tr>th.c-assign,.table thead>tr>th.c-assignedTo{padding-left:29px}.table a{vertical-align:middle}.table caption{margin-bottom:5px;background:#f1f1f1;border:none}.is-firefox .table .btn-icon-left>.icon{line-height:22px}.table tbody>tr>td.c-name a{color:#2463c7}.table tbody>tr>td.c-name a:hover{color:#1e6aeb}.table.table-data tbody>tr:hover,.table.table-form tbody>tr:hover{background:0 0}.main-table{border-radius:4px}.main-table>.table,.main-table>.table-footer,.main-table>.table-header,.main-table>.table-responsive{-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045);box-shadow:0 1px 1px rgba(0,0,0,.05),0 2px 6px 0 rgba(0,0,0,.045)}.main-table .table{font-size:13px;table-layout:fixed;background-color:#fff;border-radius:4px 4px 0 0}.main-table .table.table-lg{font-size:14px}.main-table .table .btn-icon-left{border-color:transparent}.main-table .table .btn-icon-left>.icon{background:0 0;border-radius:4px}.main-table .table .btn-icon-left.btn-sm{height:26px}.main-table .table .btn-icon-left:hover{border-color:rgba(0,0,0,.2)}.main-table .table .btn-icon-left:hover>.icon{background:#e6f0ff;border-radius:4px 0 0 4px}.main-table tbody>tr>td,.main-table thead>tr>th{min-height:36px;padding:2px 8px;line-height:30px}.main-table tbody>tr>td:first-child,.main-table thead>tr>th:first-child{padding-right:4px;padding-left:15px}.main-table thead>tr>th{overflow:hidden;text-overflow:unset;white-space:nowrap}.main-table td,.main-table th{border-bottom:none!important}.main-table thead>tr{background:#fcfdfe;border-bottom:1px solid #f4f5f7}.main-table tbody>tr:last-child>td{border-bottom:1px solid #ddd}.main-table tbody>tr>td{position:relative}.main-table tbody>tr>td .label{max-width:200px;overflow:hidden;text-overflow:unset;white-space:nowrap}.main-table tbody>tr>td>a{line-height:28px;color:#2463c7}.main-table tbody>tr>td>a:not(.btn):visited{color:#003994;opacity:.9}.main-table tbody>tr>td>a:hover,.main-table tbody>tr>td>a:visited:hover{color:#2e7fff}.main-table tbody>tr>td.c-actions{padding-right:10px}.main-table tbody>tr>td.c-side+td:before,.main-table tbody>tr>td:first-child:before{position:absolute;top:0;bottom:0;left:0;display:block;width:0;content:'';background:#2e7fff;opacity:0;-webkit-transition:.2s linear;-o-transition:.2s linear;transition:.2s linear;-webkit-transition-property:width,opacity,border-radius;-o-transition-property:width,opacity,border-radius;transition-property:width,opacity,border-radius}@-moz-document url-prefix(){.main-table tbody>tr>td.c-side+td:before,.main-table tbody>tr>td:first-child:before{bottom:-1px}}.main-table tbody>tr>td.c-side:before{display:none}.main-table tbody>tr>td.c-name a{color:#2463c7}.main-table tbody>tr>td.c-name a:hover{color:#1e6aeb}.main-table tbody>tr{border-bottom:1px solid #f4f5f7;-webkit-transition:.2s cubic-bezier(.175,.885,.32,1);-o-transition:.2s cubic-bezier(.175,.885,.32,1);transition:.2s cubic-bezier(.175,.885,.32,1);-webkit-transition-property:background-color,-webkit-box-shadow;-o-transition-property:box-shadow,background-color;transition-property:background-color,-webkit-box-shadow;transition-property:box-shadow,background-color;transition-property:box-shadow,background-color,-webkit-box-shadow}.main-table tbody>tr:hover{background:rgba(230,240,255,.8)}.main-table .table-grouped tbody>tr:hover{background:#f0f6ff;-webkit-box-shadow:none;box-shadow:none}.main-table .table-grouped tbody>tr:hover td.c-actions .more{background:#f0f6ff}.main-table tbody>tr.checked{background:#fff4ea}.main-table tbody>tr.checked:hover{background:#ffe6c6}.main-table tbody>tr.checked>td.c-side+td:before,.main-table tbody>tr.checked>td:first-child:before{width:4px;opacity:1}.main-table tbody>tr.checked.row-check-begin{border-top-left-radius:4px;border-top-right-radius:2px}.main-table tbody>tr.checked.row-check-begin>td:first-child:before{border-top-left-radius:4px}.main-table tbody>tr.checked.row-check-end{border-bottom-right-radius:2px;border-bottom-left-radius:4px}.main-table tbody>tr.checked.row-check-end>td:first-child:before{border-bottom-left-radius:4px}.main-table .checkbox-primary{display:inline-block;line-height:20px}.main-table .checkbox-primary label{margin:0}.main-table .table{margin:0}.table-header{padding:4px 0 12px}.table-header .table-statistic{color:#838a9d}.table-header .table-statistic strong{font-size:15px;color:#313c52}.table-header .btn-toolbar{margin-top:-28px}.table-header.fixed-right{position:relative;z-index:5;padding:0}.table-header.fixed-right>.btn-toolbar{position:absolute;top:0;right:1px;z-index:1;padding:1px;margin:0;background:#fff;border-radius:4px}.table-header.fixed-right>.btn-toolbar .btn{opacity:.65}.table-header.fixed-right>.btn-toolbar .btn:hover{opacity:1}.table-header-fixed .table-header{position:fixed;top:0}.table-header-fixed .table-header>.btn-toolbar{background-color:transparent}.table-header-fixed .table-header>.btn-toolbar .btn{color:#fff}.table-footer{position:relative;min-height:40px;padding:6px 15px;background:#fff;border-radius:0 0 4px 4px}.body-modal .table-footer{margin-bottom:20px}.talbe-lg+.table-footer{padding:11px 15px}.table-footer .btn-toolbar,.table-footer .checkbox-primary{float:left}.table-footer .btn-toolbar+.btn-toolbar{margin-left:8px}.table-footer .checkbox-primary{margin:5px 20px 0 0}.table-footer .checkbox-primary.checked label:after{border-color:#22c98d!important}.table-footer .btn{padding:3px 10px;line-height:20px}.table-footer .pager{position:absolute;top:0;right:0;z-index:5;height:40px;padding:6px 5px 6px 10px;margin:0;background:#fff;opacity:1;-webkit-transition:opacity .4s;-o-transition:opacity .4s;transition:opacity .4s}.table-footer .pager:before{position:absolute;top:0;bottom:0;left:-50px;display:block;width:50px;content:' ';background:-webkit-gradient(linear,left top,right top,from(rgba(255,255,255,0)),to(#fff));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0,#fff 100%);background:-o-linear-gradient(left,rgba(255,255,255,0) 0,#fff 100%);background:linear-gradient(to right,rgba(255,255,255,0) 0,#fff 100%)}.table-footer .pager .btn,.table-footer .pager>li>.pager-item,.table-footer .pager>li>.pager-label{color:#838a9d;background:0 0;border-color:transparent}.table-footer .pager .btn,.table-footer .pager>li>a{border-radius:3px}.table-footer .pager .btn:hover,.table-footer .pager>li>a:hover{background:rgba(0,0,0,.1)}.table-footer .pager>li.disabled>a.pager-item{opacity:1}.table-footer .form-control{height:28px;padding:3px 8px}.table-footer .table-statistic{position:relative;z-index:2;float:left;padding-right:30px;line-height:28px;color:#838a9d;background:#fff}.table-footer .table-statistic:hover{z-index:4}.table-footer .table-statistic:hover+.pager{z-index:2;opacity:.3}.table-footer .btn-toolbar+.table-statistic,.table-footer .btn-toolbar+.text{margin-left:10px}.table-footer .text{float:left;line-height:28px}.table-footer.fixed-footer{position:fixed;z-index:10;margin:0;background:rgba(75,75,75,.85);border-top-color:transparent}.table-footer.fixed-footer .checkbox-primary label{color:#fff}.table-footer.fixed-footer .checkbox-primary label:after{border-color:rgba(255,255,255,.8)}.table-footer.fixed-footer .table-statistic{color:#fff;background:0 0}.table-footer.fixed-footer .pager{background:#666}.table-footer.fixed-footer .pager:before{background:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,0)),to(#666));background:-webkit-linear-gradient(left,rgba(0,0,0,0) 0,#666 100%);background:-o-linear-gradient(left,rgba(0,0,0,0) 0,#666 100%);background:linear-gradient(to right,rgba(0,0,0,0) 0,#666 100%)}.table-footer.fixed-footer .pager .btn,.table-footer.fixed-footer .pager>li>.pager-item,.table-footer.fixed-footer .pager>li>.pager-label{color:#fff}.table-footer.fixed-footer .pager .btn:hover,.table-footer.fixed-footer .pager>li>a:hover{background:rgba(255,255,255,.3)}.table-footer.fixed-footer .pager>li.disabled>a.pager-item{opacity:.5}.table-actions{display:none;height:28px}.table-actions.show-always{display:block;pointer-events:none;cursor:not-allowed}.has-row-checked .table-actions{display:block;pointer-events:auto!important;cursor:default}.table-lg tbody>tr>td{padding:9px 10px}.table-lg tbody>tr>td .btn+.btn{margin-left:5px}.table.has-sort-head thead>tr>th{padding-right:0}.table.has-sort-head thead>tr>th>a{position:relative;display:inline-block;padding-right:16px;color:#313c52}.table.has-sort-head thead>tr>th>a:after,.table.has-sort-head thead>tr>th>a:before{position:absolute;top:0;right:0;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;line-height:30px;color:#313c52;text-transform:none;content:"\f0de";opacity:.5;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.table.has-sort-head thead>tr>th>a:after{content:"\f0dd"}.table.has-sort-head thead>tr>th>a.sort-down,.table.has-sort-head thead>tr>th>a.sort-up{color:#000;text-decoration:none}.table.has-sort-head thead>tr>th>a:hover,.table.has-sort-head thead>tr>th>a:hover:after,.table.has-sort-head thead>tr>th>a:hover:before{color:#2e7fff;opacity:1}.table.has-sort-head thead>tr>th>a.sort-down:after,.table.has-sort-head thead>tr>th>a.sort-up:before{color:#000;opacity:1}.head-fixed .datatable-head-span .table,.table.fixed-header-copy{z-index:10;color:#fff;background:rgba(75,75,75,.85)}.head-fixed .datatable-head-span .table thead>tr,.table.fixed-header-copy thead>tr{background:0 0}.head-fixed .datatable-head-span .table thead>tr>th,.table.fixed-header-copy thead>tr>th{color:#eee}.head-fixed .datatable-head-span .table thead>tr>th>a,.table.fixed-header-copy thead>tr>th>a{color:#eee}.head-fixed .datatable-head-span .table thead>tr>th>a:hover,.table.fixed-header-copy thead>tr>th>a:hover{color:#fff}.head-fixed .datatable-head-span .table thead>tr>th>a:after,.head-fixed .datatable-head-span .table thead>tr>th>a:before,.table.fixed-header-copy thead>tr>th>a:after,.table.fixed-header-copy thead>tr>th>a:before{color:#eee}.head-fixed .datatable-head-span .table thead>tr>th>a.sort-down,.head-fixed .datatable-head-span .table thead>tr>th>a.sort-down:after,.head-fixed .datatable-head-span .table thead>tr>th>a.sort-up,.head-fixed .datatable-head-span .table thead>tr>th>a.sort-up:before,.head-fixed .datatable-head-span .table thead>tr>th>a:hover,.head-fixed .datatable-head-span .table thead>tr>th>a:hover:after,.head-fixed .datatable-head-span .table thead>tr>th>a:hover:before,.table.fixed-header-copy thead>tr>th>a.sort-down,.table.fixed-header-copy thead>tr>th>a.sort-down:after,.table.fixed-header-copy thead>tr>th>a.sort-up,.table.fixed-header-copy thead>tr>th>a.sort-up:before,.table.fixed-header-copy thead>tr>th>a:hover,.table.fixed-header-copy thead>tr>th>a:hover:after,.table.fixed-header-copy thead>tr>th>a:hover:before{color:#fff}.head-fixed .datatable-head-span .table thead>tr>th>.dropdown>a,.table.fixed-header-copy thead>tr>th>.dropdown>a{color:#eee}.head-fixed .datatable-head-span .table thead>tr>th>.dropdown>a:hover,.table.fixed-header-copy thead>tr>th>.dropdown>a:hover{color:#fff}.head-fixed .datatable-head-span .table .checkbox-primary,.table.fixed-header-copy .checkbox-primary{z-index:1}.head-fixed .datatable-head-span .table .checkbox-primary label,.table.fixed-header-copy .checkbox-primary label{color:#fff}.head-fixed .datatable-head-span .table .checkbox-primary label:after,.table.fixed-header-copy .checkbox-primary label:after{border-color:rgba(255,255,255,.8)}.head-fixed .datatable-head-span .table .checkbox-primary.checked label:after,.table.fixed-header-copy .checkbox-primary.checked label:after{border-color:#22c98d!important}.table-data{margin:0;table-layout:fixed}.table-data tbody>tr>td,.table-data tbody>tr>th{padding:6px 8px;word-break:break-all;border:none}.table-data tbody>tr>th{width:70px;padding-left:0;font-weight:400;color:#838a9d;text-align:right;vertical-align:middle}.table-data tbody>tr>td{padding-right:0}.table-data tbody>tr>td>a{color:#2463c7}.table-data tbody>tr>td>a:not(.btn):visited{color:#003994}.table-data tbody>tr>td>a:hover,.table-data tbody>tr>td>a:visited:hover{color:#2e7fff}.table-data ol,.table-data ul{margin:0}.fixed-head-table{background:rgba(0,0,0,.7);border-bottom:1px solid #ddd}.fixed-head-table thead>tr>th{color:#fff}.table-empty-tip{padding:80px 10px;text-align:center;background:#fff}.not-firefox .table-grouped>tbody>tr>td.c-side{background:#fff!important}.table-grouped .group-toggle{cursor:pointer}.table-grouped .group-toggle.group-summary{border-top:10px solid #efefef}.table-grouped tbody>tr>td:first-child,.table-grouped thead>tr>th:first-child{padding-left:8px}.group-expand-all,.table-group-collapsed .group-collapse-all{display:none}.table-group-collapsed .group-expand-all{display:inline-block}.table-auto{table-layout:auto}.datatable .table>tbody>tr.checked.hover>td,.datatable .table>tbody>tr.checked>td.col-hover{background:#ffe6c6}body.has-fixed-footer{padding-bottom:60px}.table.with-footer-fixed{margin-bottom:20px}.table-nest-hide{display:none!important}th.table-nest-title{position:relative;padding-left:30px!important}.table-nest-icon{position:relative;display:inline-block;width:22px;height:22px;font-size:16px;color:#a6aab8;text-align:center;border-radius:4px}.table-nest-toggle:before{position:absolute;top:0;right:0;bottom:0;left:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:12px;color:#888fa1;content:"\e926";-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.table-nest-toggle:after{position:absolute;top:4px;right:4px;bottom:4px;left:4px;display:inline-block;content:"";border:1px solid;border-radius:50%}.table-nest-toggle:hover{color:#2e7fff;background-color:rgba(0,0,0,.1)}.table-nest-child-hide .table-nest-toggle:before{font-size:12px;content:"\e925"}th.table-nest-title .table-nest-toggle{position:absolute!important;top:7px;left:8px}.table-nest-toggle.table-nest-toggle-global{width:22px;height:22px;padding:0!important;line-height:22px;text-align:center;border-radius:4px}.table-nest-toggle.table-nest-toggle-global:before{position:absolute!important;font-size:12px!important;line-height:22px!important;content:"\e959"!important;opacity:1!important}.table-nest-toggle.table-nest-toggle-global:after{display:none!important}.table-nest-collapsed .table-nest-toggle.table-nest-toggle-global:before{font-size:12px!important;content:"\e95a"!important}.disable-empty-nest-row .is-nest-child .table-nest-icon:before,.disable-empty-nest-row .no-nest .table-nest-icon:before{position:relative;top:-1px;width:6px;min-width:6px;height:6px;content:' ';background-color:#c4c4c4;border-radius:1px}.table-nest-child-hover>td:first-child,.table-nest-hover>td:first-child{-webkit-box-shadow:inset 3px 0 0 #c4c4c4;box-shadow:inset 3px 0 0 #c4c4c4}.c-manager .avatar{position:relative;z-index:2;line-height:17px;border:1.5px solid transparent}.c-manager .avatar.avatar-top{border-color:#ffaf65}.c-manager a{position:absolute;top:8px;left:16px;height:20px;padding:0 8px 0 13px;line-height:20px!important;color:#313c52!important;background:#f4f5f7;border-radius:10px}.article-content{overflow:auto}.article-content img{margin-top:0}.article-content table{margin:10px 0}.article-content table td,.article-content table th{border:1px solid #cbd0db!important}.article-content table th{background:#eee}.article-content a{color:#2e7fff}.article-content a:focus,.article-content a:hover{color:#18a6fd}.article-content,.article>.content{word-wrap:break-word}.detail{padding:10px 0;margin:0 10px}.detail+.detail{padding-top:25px;border-top:1px solid #eee}.detail-title{font-size:14px;font-weight:700;line-height:20px}.detail-title>.pull-right{position:relative;top:-8px}h2.detail-title{margin:0;font-size:15px;font-weight:700}h2.detail-title .label,h2.detail-title .label-id{position:relative;top:-1px}.detail-content{padding:0;margin-top:10px}.detail-content em{color:#313c52}.detail-content .list-unstyled>li+li{margin-top:5px}.side-col .detail-content{padding-left:0}details.detail{padding:10px 0}details.detail summary{position:relative;cursor:pointer;outline:0}details.detail summary::-webkit-details-marker{display:none}details.detail summary:after{position:absolute;top:0;right:0;font-family:ZentaoIcon;font-size:14px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-transform:none;content:"\e316";opacity:.4;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}details.detail[open] summary:after{content:"\e313"}.files-list{padding-left:0;list-style:none}.files-list>li>a{display:block;line-height:24px}.files-list>li>a>.icon{display:inline-block;margin-right:5px;opacity:.7}.files-list>li>a:hover{color:#2e7fff}.files-list>li>.right-icon{opacity:0;-webkit-transition:opacity .2s;-o-transition:opacity .2s;transition:opacity .2s}.files-list>li:hover>.right-icon{opacity:1}.histories-list{padding-left:15px;margin-bottom:0}.histories-list>li{position:relative}.histories-list>li+li{margin-top:5px}.histories-list>li strong{color:#313c52}.histories-list .comment,.histories-list .show-form .comment-edit-form{padding:5px 5px 5px 10px;margin:5px 0 0;background-color:rgba(0,0,0,.025);border:1px solid #eee}.histories-list .btn-edit-comment{position:absolute;top:28px;right:2px}.histories-list .comment-edit-form,.histories-list .show-form .btn-edit-comment,.histories-list .show-form .comment{display:none}.histories-list .show-form .comment-edit-form{display:block;padding:10px;border:1px solid #eee}.histories .btn-mini{width:16px;min-width:16px;height:16px;overflow:hidden;line-height:16px;color:#cbd0db;vertical-align:-8%;border-radius:1px}.histories .btn-mini:focus,.histories .btn-mini:hover{color:#2e7fff;border-color:#2e7fff}.histories .show-changes .btn-expand>.icon:before{content:"\e926"}.histories .btn-strip{display:none}.histories .show-changes .btn-strip{display:inline-block}.history-changes{display:none;padding:5px;margin-bottom:-5px;margin-left:5px;font-size:12px;line-height:20px}.history-changes blockquote{padding:5px 5px 5px 10px;margin:5px 0 0;font-size:12px;background-color:rgba(0,0,0,.05);border-left:3px solid #eee}.history-changes blockquote.original{display:none}.show-changes .history-changes,.show-original .history-changes blockquote.original{display:block}.show-original .history-changes blockquote.textdiff{display:none}.syntaxhighlighter{overflow:auto}.list-group{overflow-y:auto}.list-group>a{display:block;padding:2px 10px 2px 5px;overflow:hidden;line-height:20px;text-overflow:unset;white-space:nowrap;border-radius:4px}.list-group>a+a{margin-top:5px}.list-group>a>.icon{display:inline-block;margin-right:3px;opacity:.5}.list-group>a.selected{color:#e6f0ff;background-color:#2e7fff}.list-group>a.active{color:#2e7fff;background-color:#e6f0ff}.list-group>a.active:hover,.list-group>a:hover{color:#fff;background-color:#2e7fff}.list-group>.heading{padding:2px 5px;line-height:20px;color:#838a9d}.list-group>a+.heading{margin-top:4px}.dropup .search-box-sink{padding-top:5px;padding-bottom:45px}.dropup .search-box-sink .search-box{position:absolute;right:10px;bottom:10px;left:10px;margin:0}.dropup .search-box-sink .search-box+.list-group{height:auto;max-height:171px}.search-list{min-width:200px;max-width:300px;padding:0}.search-list .search-box{float:none;width:auto;margin:10px}.search-list .search-box .icon-search{opacity:.5}.search-list .list-group{max-height:248px;padding:5px 10px;margin:5px 0}.dropup .search-list .search-box+.list-group{height:171px;padding-top:0}.search-list .search-input{height:30px}.search-list .input-control-icon-right{height:28px;line-height:28px}.search-list .list-group>a.active{color:inherit;background-color:inherit}.search-list.searchbox-focus .list-group>a.active{color:#2e7fff;background-color:#e6f0ff}.search-list .list-group>a.active:hover,.search-list.searchbox-focus .list-group>a.active:hover{color:#fff;background-color:#2e7fff}#dropMenu{width:initial;max-width:initial}#dropMenu>.search-box{width:100%;padding:10px 10px 0;margin:0}#dropMenu>.search-box .icon-search{color:#333}#dropMenu>.search-box.has-icon-right>.form-control{padding-left:26px}#dropMenu .input-control-icon-left{top:10px;left:10px}#dropMenu .input-control-icon-right{top:11px;right:11px}#dropMenu .input-control-icon-right .icon{position:relative;top:2px}#dropMenu .list-group{max-height:initial;margin:0}#dropMenu .table-row{margin:0 -10px;table-layout:auto}#dropMenu .table-col{position:relative;width:100%;min-width:250px;max-width:450px}#dropMenu .table-col .list-group{max-height:300px;padding:0 10px 5px}#dropMenu .col-left{padding-bottom:30px}#dropMenu .col-right{display:none}#dropMenu .col-footer{position:absolute;right:0;bottom:-5px;left:0;padding:8px 10px;border-top:1px solid #eee}#dropMenu .col-footer>a{opacity:.8}#dropMenu .col-footer>a:hover{opacity:1}#dropMenu.show-right-col .table-col{width:50%}#dropMenu.show-right-col .col-right{display:table-cell;border-left:1px solid #eee}#dropMenu.show-right-col .col-right>.list-group{max-height:335px;margin:0}#dropMenu.show-right-col .col-right>.list-group>a{opacity:.7}#dropMenu.show-right-col .col-right>.list-group>a:hover{opacity:1}#dropMenu.show-right-col .toggle-right-col>.icon-angle-right:before{content:"\e314"}#dropMenu.has-search-text .list-group{overflow-x:hidden}#dropMenu.has-search-text>.search-box{width:100%!important}#dropMenu.has-search-text>.list-group>.table-row{display:block}#dropMenu.has-search-text>.list-group>.table-row>.table-col{display:block;width:100%}#dropMenu.has-search-text .col-left{padding-bottom:0}#dropMenu.has-search-text .pull-right.toggle-right-col{display:none}#dropMenu.has-search-text .col-left .list-group{margin-bottom:0}#dropMenu.has-search-text .col-right .list-group{margin-bottom:0}#dropMenu.has-search-text .col-right .list-group>a{opacity:.7}#dropMenu.has-search-text .col-footer,#dropMenu.has-search-text .hide-in-search{display:none}#swapper{position:relative}#swapper #dropMenu .tree li>.selected{color:#2e7fff!important;background:#e6f0ff!important}#swapper #dropMenu .list-group>a.active:hover,#swapper #dropMenu .search-box .list-group>a.active,#swapper #dropMenu .tree li>.clickable:hover,#swapper #dropMenu.searchbox-focus .list-group>a.active:hover{color:#fff!important;background:#2e7fff!important}#swapper #dropMenu .tree{margin:0}#pageNav #dropMenu .list-group>a.active{color:inherit;background-color:inherit}#pageNav #dropMenu .list-group>a.active:hover,#pageNav #dropMenu.searchbox-focus .list-group>a.active:hover{color:#2e7fff!important;background:#e6f0ff!important}#pageNav #dropMenu .list-group>a.active.selected,#pageNav #dropMenu .list-group>a.active.selected:hover{color:#fff!important;background:#2e7fff!important}.release-path{overflow:hidden}.release-line{display:table;width:100%;padding:0;table-layout:fixed}.release-line>li{display:table-cell;list-style:none}.release-line>li>a{position:relative;display:block}.release-line>li>a:before{position:absolute;left:0;display:block;width:13px;height:13px;content:' ';background:#fff;border:2px solid #838a9d;border-radius:50%}.release-line>li>a:after{position:absolute;left:5px;display:block;width:2px;height:30px;content:' ';background:#c2dbff}.release-line>li>a>.icon{position:absolute;left:4px;font-size:24px}.release-line>li>a .title{display:block;font-size:14px;white-space:nowrap}.release-line>li>a .date,.release-line>li>a .info{display:block;max-height:18px;overflow:hidden;font-size:12px;color:#838a9d;text-overflow:unset;white-space:nowrap}.release-line>li>a:hover:before{background-color:#e6f0ff}.release-line>li>a:hover:after{background-color:#838a9d}.release-line>li>a:hover .title{color:#2e7fff}.release-line>li>a:hover .date,.release-line>li>a:hover .info{color:#838a9d}.release-line>li:nth-child(odd){padding-top:80px;vertical-align:top}.release-line>li:nth-child(odd)>a{height:85px;padding-top:36px;border-top:5px solid #c2dbff}.release-line>li:nth-child(odd)>a:before{top:-9px}.release-line>li:nth-child(odd)>a:after{top:6px}.release-line>li:nth-child(odd)>a>.icon{top:-26px}.release-line>li:nth-child(even){padding-bottom:80px;vertical-align:bottom}.release-line>li:nth-child(even)>a{height:85px;padding-bottom:36px;border-bottom:5px solid #c2dbff}.release-line>li:nth-child(even)>a:before{bottom:-9px}.release-line>li:nth-child(even)>a:after{bottom:6px}.release-line>li:nth-child(even)>a>.icon{bottom:-2px}.release-line>li:last-child>a{border-color:transparent}.release-line>li.active>a:before{border-color:#2e7fff}.release-line>li+li>a>.date,.release-line>li+li>a>.info,.release-line>li+li>a>.title{position:relative;left:-36%}#footer{position:fixed;right:0;bottom:0;left:0;z-index:1010;height:40px;background:#fff;border-top:1px solid #eff1f7}#footer .breadcrumb{padding:10px 0;margin:0}#footer .breadcrumb>li{max-width:200px;overflow:hidden;text-overflow:unset;white-space:nowrap}#footer .breadcrumb>.active,#footer .breadcrumb>li>a{color:#838a9e}#footer .breadcrumb>.active>.icon,#footer .breadcrumb>li>a>.icon{display:none}#footer .breadcrumb>.active:hover,#footer .breadcrumb>li>a:hover{color:#18a6fd}#footer .breadcrumb>li+li:before{content:'>'}#footer>.container{padding:0 20px}@media (min-width:1400px){#footer>.container{padding:0 40px}}#poweredBy{position:absolute;top:4px;right:0;padding:5px 10px}#poweredBy .icon-zentao{color:#0097fd}#poweredBy a{color:#313c52}#poweredBy a:hover{color:#2e7fff}#poweredBy a:hover .icon-zentao{color:#2e7fff}#poweredBy a.text-important{color:#ff6f42}#poweredBy a.text-important:hover{color:#fc5959}#poweredBy a.text-primary{color:#2e7fff}#poweredBy a.text-primary:hover{color:#18a6fd}#poweredBy #aiux{color:#cbd0dc}#noticeBox .alert{-webkit-box-shadow:rgba(0,0,0,.15) 0 3px 10px,rgba(0,0,0,.25) 0 3px 10px;box-shadow:rgba(0,0,0,.15) 0 3px 10px,rgba(0,0,0,.25) 0 3px 10px}#heading{top:0}.header-btn{position:relative;padding:8px 0}.header-btn .btn{position:relative;height:34px;padding:1px 6px;margin:0;overflow:visible;font-size:13px;font-weight:400;line-height:28px;color:#fff;background-color:transparent;border-color:transparent!important;border-right:none;-webkit-transition:none;-o-transition:none;transition:none}.header-btn .btn>.caret{margin-left:0;border-width:4px}.header-btn .btn>.text{display:inline-block;max-width:150px;overflow:hidden;text-overflow:unset;vertical-align:middle}.header-btn .btn:hover{-webkit-box-shadow:none;box-shadow:none}.header-btn .btn:hover,.header-btn.active .btn{color:#fff;background:rgba(0,0,0,.15)}.header-btn .btn:hover>.caret,.header-btn.active .btn>.caret{opacity:1}.header-btn+.header-btn{margin-left:10px}.header-btn+.header-btn:before{position:absolute;top:16px;left:-13px;display:block;font-family:ZentaoIcon;font-size:14px;font-size:16px;font-style:normal;font-weight:400;font-variant:normal;line-height:1;text-transform:none;content:"\e315";opacity:.6;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.header-btn .dropdown-menu{margin-top:-10px}#toolbar{top:0;height:50px}#userNav>li{margin-right:0}#userNav>li>a{padding:10px 5px}#userNav>li>a>.icon{font-size:30px;filter:brightness(1.2) hue-rotate(30deg);opacity:.9;-webkit-filter:brightness(1.2) hue-rotate(30deg)}#userNav>li:hover>a{background-color:rgba(0,0,0,.1)}#userNav .dropdown-menu>li>a{position:relative;padding-left:24px}#userNav .dropdown-menu>li>a>.icon{top:1px;left:0}#userNav .dropdown-menu>li.user-profile-item>a{padding-left:45px}
diff --git a/www/theme/zui/fonts/ZentaoIcon.eot b/www/theme/zui/fonts/ZentaoIcon.eot
index a21b287b39..f8471b08ed 100644
Binary files a/www/theme/zui/fonts/ZentaoIcon.eot and b/www/theme/zui/fonts/ZentaoIcon.eot differ
diff --git a/www/theme/zui/fonts/ZentaoIcon.svg b/www/theme/zui/fonts/ZentaoIcon.svg
index c331ff99a6..d712201603 100644
--- a/www/theme/zui/fonts/ZentaoIcon.svg
+++ b/www/theme/zui/fonts/ZentaoIcon.svg
@@ -1,279 +1,282 @@
-
-
-
-Generated by IcoMoon
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+Generated by IcoMoon
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/www/theme/zui/fonts/ZentaoIcon.ttf b/www/theme/zui/fonts/ZentaoIcon.ttf
index a006de2ecc..d37b9d53ce 100644
Binary files a/www/theme/zui/fonts/ZentaoIcon.ttf and b/www/theme/zui/fonts/ZentaoIcon.ttf differ
diff --git a/www/theme/zui/fonts/ZentaoIcon.woff b/www/theme/zui/fonts/ZentaoIcon.woff
index 202c17aff9..b9af504599 100644
Binary files a/www/theme/zui/fonts/ZentaoIcon.woff and b/www/theme/zui/fonts/ZentaoIcon.woff differ
diff --git a/xuanxuan/XUANVERSION b/xuanxuan/XUANVERSION
index 15c65ccf5b..3f379dbd01 100644
--- a/xuanxuan/XUANVERSION
+++ b/xuanxuan/XUANVERSION
@@ -1 +1 @@
-a975bd7e7e1fd29bc95ee7b9c5bafdc94a4e9eb0
+6d5d77c11978c707cb8204353f640a3ecca6e046
diff --git a/xuanxuan/XVERSION b/xuanxuan/XVERSION
index 4074fe2027..4fedf1d20e 100644
--- a/xuanxuan/XVERSION
+++ b/xuanxuan/XVERSION
@@ -1 +1 @@
-6.6
+7.0
diff --git a/xuanxuan/extension/xuan/im/ext/bot/zentao.bot.php b/xuanxuan/extension/xuan/im/ext/bot/zentao.bot.php
new file mode 100644
index 0000000000..82b6f7af82
--- /dev/null
+++ b/xuanxuan/extension/xuan/im/ext/bot/zentao.bot.php
@@ -0,0 +1,1008 @@
+name = '禅道';
+ if($_SESSION['clientLang'] == 'zh-tw') $this->name = '禪道';
+ }
+ }
+
+ /**
+ * Init bot, load model.
+ *
+ * @access public
+ * @return void
+ */
+ public function init()
+ {
+ $this->lang = $this->im->lang->im->bot->zentaoBot;
+
+ foreach(array('view', 'start', 'close', 'finish') as $command)
+ {
+ $this->commands[] = array('command' => $command, 'alias' => $this->lang->commands->$command->alias, 'description' => $this->lang->commands->$command->description, 'internal' => true);
+ }
+
+ $this->im->loadModel('task');
+ $this->im->loadModel('user');
+ $this->im->app->loadClass('pager', $static = true);
+
+ $this->users = array_filter($this->im->user->getPairs('noclosed|noletter'));
+ $this->taskStatusList = array_filter($this->im->lang->task->statusList);
+ $this->help = $this->lang->help;
+
+ $this->inited = true;
+ }
+
+ /**
+ * Close command.
+ *
+ * @param array $args bot command arguments
+ * @access public
+ * @return object|string
+ */
+ public function close($args = array())
+ {
+ $args = $this->removeEqualSign($args);
+ foreach($args as $key => $value)
+ {
+ if(in_array(strtolower($value), $this->lang->condKeywords['task'])) unset($args[$key]);
+ }
+ $task = $this->closeTask($args);
+ return $task;
+ }
+
+ /**
+ * Finish command.
+ *
+ * @param array $args bot command arguments
+ * @access public
+ * @return object|string
+ */
+ public function finish($args = array())
+ {
+ $originArgs = $args;
+ $args = array_filter(explode('=', implode('=', $args)));
+ foreach($args as $key => $value)
+ {
+ if(in_array(strtolower($value), $this->lang->condKeywords['task'])) unset($args[$key]);
+ }
+ return $this->finishTask($args, $originArgs);
+ }
+
+ /**
+ * View command.
+ *
+ * @param array $args bot command arguments
+ * @param int $userID
+ * @param object $user
+ * @access public
+ * @return object|string
+ */
+ public function view($args = array(), $userID = 0, $user = null)
+ {
+ $pager = pager::init(0, 10, 1);
+
+ $pageSearchRegex = $this->lang->pageSearchRegex;
+ $pageIDKeywords = $this->lang->condKeywords['pageID'];
+
+ $args = array_filter($args, function($arg) use ($pager, $pageSearchRegex, $pageIDKeywords)
+ {
+ $matches = array();
+ preg_match($pageSearchRegex, $arg, $matches);
+ if(count($matches) > 2)
+ {
+ if(in_array(strtolower($matches[1]), $pageIDKeywords))
+ {
+ $pager->pageID = $matches[2];
+ }
+ else
+ {
+ $pager->recPerPage = $matches[2];
+ }
+ return false;
+ }
+ return true;
+ });
+ $originArgs = $args;
+
+ $args = $this->removeEqualSign($args);
+
+ foreach($args as $key => $value)
+ {
+ if(in_array(strtolower($value), $this->lang->condKeywords['task']))
+ {
+ unset($args[$key]);
+ $tasks = $this->viewTask($args, $user, $pager);
+ return $this->renderTask($tasks, $originArgs, $pager);
+ }
+ }
+ }
+
+ /**
+ * Parse bot command arguments.
+ *
+ * @param array $args bot command arguments
+ * @param array $keys
+ * @access public
+ * @return object
+ */
+ public function parseArguments($args = array(), $keys = array())
+ {
+ $assignedToList = '';
+ $priList = '';
+ $statusList = '';
+ $idList = '';
+ $taskName = '';
+ $comment = '';
+ while(!empty($args))
+ {
+ $arg = array_shift($args);
+ if(isset($keys['pri']))
+ {
+ if(in_array(strtolower($arg), $this->lang->condKeywords['pri']))
+ {
+ $pris = array_shift($args);
+ $priList = $this->buildCondParamByModifier($pris, $priList, 'p');
+ continue;
+ }
+ if(preg_match('/p[1-4]/i', $arg) == 1)
+ {
+ $priList = $this->buildCondParamByModifier($arg, $priList, 'p');
+ continue;
+ }
+ }
+ if(isset($keys['id']))
+ {
+ if(in_array(strtolower($arg), $this->lang->condKeywords['id']))
+ {
+ $ids = array_shift($args);
+ $idList .= $this->buildCondParamByModifier($ids, $idList, '#');
+ continue;
+ }
+ if(preg_match('/#\d+/', $arg) == 1)
+ {
+ $idList .= $this->buildCondParamByModifier($arg, $idList, '#');
+ continue;
+ }
+ }
+ if(isset($keys['status']))
+ {
+ if(in_array(strtolower($arg), $this->lang->condKeywords['status']))
+ {
+ $statuses = array_shift($args);
+ $statusList = $this->buildCondParam($statuses, $statusList, $this->taskStatusList);
+ continue;
+ }
+ if($this->checkCondType($arg, $this->taskStatusList))
+ {
+ $statusList = $this->buildCondParam($arg, $statusList, $this->taskStatusList);
+ continue;
+ }
+ }
+ if(isset($keys['assignTo']))
+ {
+ if(in_array(strtolower($arg), $this->lang->condKeywords['assignTo']))
+ {
+ $usernames = array_shift($args);
+ $assignedToList = $this->buildCondParam($usernames, $assignedToList, $this->users);
+ continue;
+ }
+ if($this->checkCondType($arg, $this->users))
+ {
+ $assignedToList = $this->buildCondParam($arg, $assignedToList, $this->users);
+ continue;
+ }
+ }
+ if(isset($keys['comment']))
+ {
+ if(in_array(strtolower($arg), $this->lang->condKeywords['comment']))
+ {
+ $comment = array_shift($args);
+ }
+ else {
+ $comment = $arg;
+ }
+ continue;
+ }
+ if(isset($keys['taskName']))
+ {
+ if(in_array(strtolower($arg), $this->lang->condKeywords['taskName']))
+ {
+ $taskName = array_shift($args);
+ }
+ else
+ {
+ $taskName = $arg;
+ }
+ }
+ }
+
+ $conds = new stdClass();
+ if(isset($keys['pri'])) $conds->priList = trim($priList, ',');
+ if(isset($keys['assignTo'])) $conds->assignedToList = trim($assignedToList, ',');
+ if(isset($keys['status'])) $conds->statusList = trim($statusList, ',');
+ if(isset($keys['id'])) $conds->idList = trim($idList, ',');
+ if(isset($keys['taskName'])) $conds->taskName = $taskName;
+ if(isset($keys['comment'])) $conds->comment = $comment;
+ return $conds;
+ }
+
+ /**
+ * Check condition type.
+ *
+ * @param string $arg
+ * @param array $list
+ * @access public
+ * @return bool
+ */
+ public function checkCondType($arg, $list)
+ {
+ $args = explode(',', $arg);
+ $array_inter = array_intersect($args, $list);
+ if(!empty($array_inter)) return true;
+
+ $list = array_flip($list);
+ $array_inter = array_intersect($args, $list);
+ if(!empty($array_inter)) return true;
+
+ return false;
+ }
+
+ /**
+ * Build condition param.
+ *
+ * @param string $arg bot command arguments
+ * @param string $condParam
+ * @param array $condArr
+ * @access public
+ * @return string
+ */
+ public function buildCondParam($arg, $condParam, $condArr)
+ {
+ $arr = explode(',', $arg);
+ foreach($arr as $cond)
+ {
+ if(array_key_exists($cond, $condArr))
+ {
+ $condParam .= ',' . $cond;
+ }
+ else
+ {
+ $key = array_search($cond, $condArr);
+ if($key !== false) $condParam .= ',' . $key;
+ }
+ }
+ return $condParam;
+ }
+
+ /**
+ * Remove equal sign from bot command arguments.
+ *
+ * @param array $args bot command arguments
+ * @access public
+ * @return array
+ */
+ public function removeEqualSign($args)
+ {
+ return array_filter(explode('=', implode('=', $args)));
+ }
+
+ /**
+ * Build condition param by modifier.
+ *
+ * @param array $args bot command arguments
+ * @param string $condParam
+ * @param string $modifier
+ * @access public
+ * @return string
+ */
+ public function buildCondParamByModifier($arg, $condParam, $modifier)
+ {
+ $arg = strtolower($arg);
+ $arg = str_replace($modifier, '', $arg);
+ $condParam .= ',' . $arg;
+ return $condParam;
+ }
+
+ /**
+ * View task command.
+ *
+ * @param array $args bot command arguments
+ * @param object $user
+ * @param object $pager
+ * @access public
+ * @return array
+ */
+ public function viewTask($args = array(), $user = null, $pager = null)
+ {
+ $keys = array();
+ foreach(array('pri', 'id', 'status', 'assignTo', 'taskName') as $key) $keys[$key] = true;
+
+ if(empty($args))
+ {
+ $args['assignTo'] = is_object($user) ? $user->account : false;
+ $args['status'] = 'wait,doing,done,pause,cancel';
+ }
+
+ $conds = $this->parseArguments($args, $keys);
+ $conds->search = 1;
+ $conds->order = 'status_asc';
+ $conds->limit = 10;
+
+ if(is_object($pager))
+ {
+ $conds->page = $pager->pageID;
+ $conds->limit = $pager->recPerPage;
+ }
+
+ $result = $this->loadEntry('tasks', 'get', array(), (array)$conds);
+
+ if(isset($result->tasks))
+ {
+ $pager->setRecTotal($result->total);
+ $pager->setRecPerPage($result->limit);
+ $pager->setPageID($result->page);
+ $pager->setPageTotal();
+ return $result->tasks;
+ }
+
+ return array();
+ }
+
+ /**
+ * Close task command.
+ *
+ * @param array $args bot command arguments
+ * @access public
+ * @return object|string
+ */
+ public function closeTask($args)
+ {
+ $keys = array();
+ $keys['id'] = true;
+ $keys['comment'] = true;
+
+ $conds = $this->parseArguments($args, $keys);
+ $tasks = array_filter(explode(',', $conds->idList));
+ $taskID = array_pop($tasks);
+ $comment = $conds->comment;
+
+ if(!is_numeric($taskID)) return $this->lang->errors->invalidCommand;
+
+ $task = $this->loadEntry('task', 'get', array('taskID'=> $taskID));
+ if(empty($task)) return $this->lang->errors->emptyResult;
+
+ if($task->status != 'done' && $task->status != 'cancel') return sprintf($this->lang->errors->invalidStatus, $this->taskStatusList[$task->status]);
+
+ $task = $this->loadEntry('taskclose', 'post', array('taskID' => $taskID, 'comment' => $comment));
+ if($task->result == 'fail') return $task->message;
+
+ $link = str_replace('x.php', 'index.php', helper::createLink('task', 'view', "taskID=$taskID", 'html'));
+ $messages = new stdClass();
+ $messages->type = 'url';
+ $messages->url = common::getSysURL() . $link;
+ return array($this->lang->success, $messages);
+ }
+
+ /**
+ * Finish task.
+ *
+ * @param array $args
+ * @param array $originArgs
+ * @access public
+ * @return object|string
+ */
+ private function finishTask($args, $originArgs)
+ {
+ $taskID = '';
+ $params = array();
+ while(!empty($args))
+ {
+ $arg = array_shift($args);
+ if(in_array(strtolower($arg), $this->lang->condKeywords['id']))
+ {
+ $taskID = array_shift($args);
+ $taskID = str_replace('#', '', $taskID);
+ }
+ elseif(preg_match('/#\d+/', $arg) == 1)
+ {
+ $taskID = str_replace('#', '', $arg);
+ }
+ else
+ {
+ parse_str(str_replace('amp;', '', end($originArgs)), $params);
+ }
+ }
+
+ if(!is_numeric($taskID)) return $this->lang->errors->invalidCommand;
+
+ $task = $this->loadEntry('task', 'get', array('taskID'=> $taskID));
+ if(!common::hasPriv('task', 'finish')) return $this->lang->errors->unauthorized;
+
+ if(empty($task) || isset($task->error)) return $this->lang->errors->emptyResult;
+ if($task->status != 'wait' && $task->status != 'doing') return sprintf($this->lang->errors->invalidStatus, $this->taskStatusList[$task->status]);
+
+ $reply = new stdClass();
+ $reply->messages = array();
+ $reply->responses = array();
+
+ $realStarted = $this->formatDate($task, 'realStarted');
+ $finishedDate = $this->formatDate($task, 'finishedDate');
+
+ if(empty($params))
+ {
+ $originCommand = rawurlencode($this->lang->finishCommand . ' ' . implode(' ', $originArgs));
+
+ $json = (object)array
+ (
+ 'title' => "#{$taskID} {$task->name}",
+ 'submitLabel' => $this->im->lang->task->finish,
+ 'inputs' =>
+ array(
+ (object)array
+ (
+ 'name' => $this->im->lang->task->hasConsumed,
+ 'type' => 'input',
+ 'label' => $this->im->lang->task->hasConsumed,
+ 'value' => $task->consumed,
+ 'disabled' => true,
+ 'addon' => $this->im->lang->task->hour,
+ ),
+ (object)array
+ (
+ 'name' => $this->im->lang->task->currentConsumed,
+ 'type' => 'input',
+ 'label' => $this->im->lang->task->currentConsumed,
+ 'value' => 0,
+ 'addon' => $this->im->lang->task->hour,
+ ),
+ (object)array
+ (
+ 'name' => $this->im->lang->task->realStarted,
+ 'type' => 'datetime-local',
+ 'label' => $this->im->lang->task->realStarted,
+ 'value' => $realStarted,
+ ),
+ (object)array
+ (
+ 'name' => $this->im->lang->task->finishedDate,
+ 'type' => 'datetime-local',
+ 'label' => $this->im->lang->task->finishedDate,
+ 'value' => $finishedDate,
+ ),
+ ),
+ );
+ $json = rawurlencode(json_encode($json));
+
+ $reply->messages[] = $this->lang->finish->tip;
+ $reply->messages[] = "[{$this->lang->finish->tipLinkTitle}](xxc://openFormAndSendToServerBySendbox/$json/$originCommand)";
+ }
+ else
+ {
+ $messageID = isset($params['messageId']) ? $params['messageId'] : '';
+ unset($params['messageId']);
+
+ $currentConsumed = 0;
+
+ foreach($params as $key => $value)
+ {
+ if(in_array(strtolower($key), $this->lang->condKeywords['realStarted'])) $realStarted = $value;
+ if(in_array(strtolower($key), $this->lang->condKeywords['finishedDate'])) $finishedDate = $value;
+ if(in_array(strtolower($key), $this->lang->condKeywords['currentConsumed'])) $currentConsumed = $value;
+ };
+
+ if(empty($realStarted) || empty($finishedDate)) return '';
+
+ $params = array
+ (
+ 'currentConsumed' => $currentConsumed,
+ 'realStarted' => $realStarted,
+ 'finishedDate' => $finishedDate,
+ );
+
+ $task = $this->loadEntry('taskfinish', 'post', array_merge(array('taskID' => $taskID, 'assignedTo' => '', 'comment' => ''), $params));
+ if(isset($task->error))
+ {
+ foreach($task->error as $error) $reply->messages[] = is_array($error) ? implode(',', $error): $error;
+ return $reply;
+ }
+
+ $finishedDate = $this->formatDate($task, 'finishedDate', '', 'Y-m-d H:i:s');
+
+ $reply->messages[] = sprintf($this->lang->finish->done, $taskID, $finishedDate, $task->consumed);
+ $reply->messages[] = $this->renderTaskTable(array($task));
+
+ $reply->responses[] = $this->disableMarkdownLinkInMessage($messageID);
+
+ if(!empty($task->fromBug))
+ {
+ $reply->messages[] = sprintf($this->lang->finish->bugTip, $taskID);
+ $link = commonModel::getSysURL() . str_replace('x.php', 'index.php', helper::createLink('bug', 'view', "bugID={$task->fromBug}", 'html'));
+ $href = urlencode($link);
+ $reply->messages[] = "[{$this->lang->finish->bugTipLinkTitle}](xxc:openInApp/zentao-integrated/{$href})";
+ }
+ }
+
+ return $reply;
+ }
+
+ /**
+ * Format date.
+ *
+ * @param object $task
+ * @param string $field
+ * @param string $default
+ * @param string $format
+ * @return string
+ */
+ private function formatDate($task, $field, $default = '', $format = 'Y-m-d\TH:i')
+ {
+ try
+ {
+ $datetime = new DateTime(empty($task->$field) ? 'now' : $task->$field, new DateTimeZone('UTC'));
+ $datetime->setTimezone(new DateTimeZone(date_default_timezone_get()));
+ return $datetime->format($format);
+ }
+ catch(Exception $e)
+ {
+ return $default;
+ }
+ }
+
+ /**
+ * Render tasks as table html.
+ *
+ * @param array $tasks
+ * @param array $originArgs
+ * @param pager $pager
+ * @return string|array
+ */
+ public function renderTask($tasks, $originArgs, $pager)
+ {
+ $lang = $this->im->lang;
+
+ $taskCount = $pager->recTotal ? $pager->recTotal : count($tasks);
+
+ if($taskCount === 0) return $lang->task->noTask;
+
+ if($taskCount == 1)
+ {
+ $task = current($tasks);
+ $link = str_replace('x.php', 'index.php', helper::createLink('task', 'view', "taskID=$task->id", 'html'));
+
+ $messages = new stdclass();
+ $messages->type = 'url';
+ $messages->url = common::getSysURL() . $link;
+ return array(sprintf($this->lang->tasksFound, $taskCount), $messages);
+ }
+
+ $messages = array();
+ if($pager->pageID == 1) $messages[] = sprintf($this->lang->tasksFound, $taskCount);
+
+ $taskTable = $this->renderTaskTable($tasks);
+
+ $originCommand = $this->lang->viewCommand . ' ' . implode(' ', $originArgs);
+ $paging = "$pager->pageID / $pager->pageTotal";
+ if($pager->pageID != 1)
+ {
+ $previousPageCommand = $originCommand . " {$this->lang->condKeywords['pageID'][1]}=" . ($pager->pageID - 1) . " {$this->lang->condKeywords['recPerPage'][1]}=" . $pager->recPerPage;
+ $previousPageCommand = rawurlencode($previousPageCommand);
+ $paging .= " [{$this->lang->prevPage}](xxc://sendContentToServerBySendbox/{$previousPageCommand})";
+ }
+
+ if($pager->pageID != $pager->pageTotal)
+ {
+ $nextPageCommand = $originCommand . " {$this->lang->condKeywords['pageID'][1]}=" . ($pager->pageID + 1) . " {$this->lang->condKeywords['recPerPage'][1]}=" . $pager->recPerPage;
+ $nextPageCommand = rawurlencode($nextPageCommand);
+ $paging .= " [{$this->lang->nextPage}](xxc://sendContentToServerBySendbox/{$nextPageCommand})";
+ }
+ $paging = "$paging
";
+
+ $messages[] = $taskTable . $paging;
+
+ return $messages;
+ }
+
+ /**
+ * Render tasks as markdown table.
+ *
+ * @param array $tasks
+ * @access public
+ * @return void
+ */
+ public function renderTaskTable($tasks)
+ {
+ $lang = $this->im->lang;
+ $headMap = array('id'=>'ID', 'pri' => $lang->task->pri, 'name' => $lang->task->name, 'assignedTo' => $lang->task->assignedTo, 'status' => $lang->task->status, 'estimate' => $lang->task->estimateAB, 'consumed' => $lang->task->consumedAB, 'left' => $lang->task->leftAB, 'actions' => $lang->actions);
+ $thead = '';
+
+ foreach($headMap as $value) $thead .= " {$value} ";
+
+ $thead = "{$thead} ";
+ $tbody = '';
+ foreach($tasks as $task)
+ {
+ $tr = '';
+ $link = str_replace('x.php', 'index.php', helper::createLink('task', 'view', "taskID=$task->id", 'html'));
+ $href = urlencode(common::getSysURL() . $link);
+ $isParent = $task->parent == -1;
+ $isChild = $task->parent > 0;
+ $isMulti = !empty($task->mode);
+ foreach($headMap as $key => $value)
+ {
+ switch($key)
+ {
+ case 'name':
+ $tr .= "";
+ if($isParent) $tr .= " {$lang->task->parentAB} ";
+ if($isChild) $tr .= " {$lang->task->childrenAB} ";
+ if($isMulti) $tr .= " " . zget($lang->task->modeList, $task->mode) . " ";
+ $tr .= "{$task->$key} ";
+ $tr .= " ";
+ break;
+ case 'status':
+ $tr .= "{$this->taskStatusList[$task->$key]} ";
+ break;
+ case 'assignedTo':
+ if($task->mode == 'multi')
+ {
+ $tr .= "{$lang->task->team} ";
+ }
+ else if($task->$key == 'closed')
+ {
+ $tr .= "Closed ";
+ }
+ else
+ {
+ $tr .= "{$task->assignedTo->realname} ";
+ }
+ break;
+ case 'actions':
+ $startUrl = 'xxc://sendContentToServerBySendbox/' . "{$this->lang->startCommand} #$task->id";
+ $finishUrl = 'xxc://sendContentToServerBySendbox/' . "{$this->lang->finishCommand} #$task->id";
+ $closeUrl = 'xxc://sendContentToServerBySendbox/' . "{$this->lang->closeCommand} #$task->id";
+
+ $canStart = in_array($task->status, array('wait', 'pause'));
+ $canFinish = in_array($task->status, array('wait', 'pause', 'doing'));
+ $canClose = in_array($task->status, array('done', 'cancel'));
+
+ /* Disable all if task has children. */
+ if($isParent)
+ {
+ $canStart = false;
+ $canFinish = false;
+ $canClose = false;
+ }
+
+ $tr .= '';
+ $tr .= $canStart ? "
" : "
";
+ $tr .= $canFinish ? "
" : "
";
+ $tr .= $canClose ? "
" : "
";
+ $tr .= '
';
+ break;
+ case 'estimate':
+ case 'consumed':
+ case 'left':
+ $tr .= "{$task->$key}h ";
+ break;
+ default:
+ $tr .= "{$task->$key} ";
+ }
+ }
+ $tbody .= "{$tr} ";
+ }
+ return "";
+ }
+
+ /**
+ * Start task command.
+ *
+ * @param array $args bot command arguments
+ * @param int $userID
+ * @access public
+ * @return object|string
+ */
+ public function start($args = array(), $userID = 0)
+ {
+ $originArgs = $args;
+ foreach($args as $key => $value)
+ {
+ if(in_array(strtolower($value), $this->lang->condKeywords['task']))
+ {
+ unset($args[$key]);
+ }
+ }
+
+ if(count($args) == 0) return $this->lang->errors->taskIDRequired;
+
+ $taskID = 0;
+ foreach($args as $key => $value)
+ {
+ if(strpos($value, '#') !== false)
+ {
+ $id = str_replace('#', '', $value);
+ if($id && is_numeric($id))
+ {
+ $taskID = $id;
+ unset($args[$key]);
+ }
+ else
+ {
+ if($args[$key + 1] && is_numeric($args[$key + 1]))
+ {
+ $taskID = $args[$key + 1];
+ unset($args[$key]);
+ unset($args[$key + 1]);
+ }
+ }
+ }
+ }
+ if(!$taskID) return $this->lang->errors->taskIDRequired;
+
+ $task = $this->loadEntry('task', 'get', array('taskID' => $taskID));
+ if(!$task) return $this->lang->errors->taskNotFound;
+ if($task->status != 'wait') return sprintf($this->lang->errors->invalidStatus, $this->taskStatusList[$task->status]);
+
+ $reply = new stdClass();
+ $reply->messages = array();
+ $reply->responses = array();
+
+ if(count($args) == 0)
+ {
+ $originCommand = rawurlencode($this->lang->startCommand . ' ' . implode(' ', $originArgs));
+
+ $realStarted = $this->formatDate($task, 'realStarted');
+
+ $json = (object)array
+ (
+ 'title' => "#{$taskID} {$task->name}",
+ 'submitLabel' => $this->im->lang->task->start,
+ 'inputs' => array
+ (
+ (object)array
+ (
+ 'name' => $this->im->lang->task->realStarted,
+ 'type' => 'datetime-local',
+ 'label' => $this->im->lang->task->realStarted,
+ 'value' => $realStarted,
+ ),
+ (object)array
+ (
+ 'name' => $this->im->lang->task->consumed,
+ 'type' => 'number',
+ 'label' => $this->im->lang->task->consumed,
+ 'value' => $task->consumed,
+ 'addon' => $this->im->lang->task->hour,
+ ),
+ (object)array
+ (
+ 'name' => $this->im->lang->task->left,
+ 'type' => 'number',
+ 'label' => $this->im->lang->task->left,
+ 'value' => $task->left,
+ 'addon' => $this->im->lang->task->hour,
+ 'helpText' => $this->lang->start->finishWithZeroLeft,
+ )
+ ),
+ );
+ $json = rawurlencode(json_encode($json));
+
+ $reply->messages[] = sprintf($this->lang->start->tip, $taskID);
+ $reply->messages[] = "[{$this->lang->start->tipLinkTitle}](xxc://openFormAndSendToServerBySendbox/$json/$originCommand)";
+ }
+ else
+ {
+ $hasFormArgs = false;
+
+ $left = $task->left;
+ $consumed = $task->consumed;
+ $realStarted = $task->realStarted;
+
+ foreach($args as $arg)
+ {
+ if(strpos($arg, 'messageId'))
+ {
+ $hasFormArgs = true;
+ $formArgs = explode('&', $arg);
+ $replyArgs = array();
+ foreach($formArgs as $formArg)
+ {
+ $item = explode('=', $formArg);
+ if(count($item) == 2)
+ {
+ $replyArgs[$item[0]] = $item[1];
+ }
+ }
+ foreach($replyArgs as $key => $value)
+ {
+ if(in_array(strtolower($key), $this->lang->condKeywords['consumed'])) $consumed = $value;
+ if(in_array(strtolower($key), $this->lang->condKeywords['realStarted'])) $realStarted = $value;
+ if(in_array(strtolower($key), $this->lang->condKeywords['left'])) $left = $value;
+ }
+ if($consumed == 0 && $left == 0) return $this->im->lang->task->noticeTaskStart;
+
+ $task = $this->loadEntry('taskStart', 'post', array
+ (
+ 'taskID' => $taskID,
+ 'left' => $left,
+ 'realStarted' => $realStarted,
+ 'consumed' => $consumed,
+ ));
+ if($task->result and $task->result == 'fail')
+ {
+ $reply->messages[] = $task->message;
+ }
+ elseif(isset($task->error))
+ {
+ foreach($task->error as $error) $reply->messages[] = is_array($error) ? implode(',', $error): $error;
+ }
+ else
+ {
+ $reply->messages[] = sprintf($this->lang->effortRecorded, $taskID);
+ $reply->messages[] = $this->renderTaskTable(array($task));
+
+ $reply->responses[] = $this->disableMarkdownLinkInMessage($replyArgs['messageId']);
+ }
+ }
+ }
+
+ if(!$hasFormArgs)
+ {
+ while(!empty($args))
+ {
+ $arg = array_shift($args);
+ if(in_array(strtolower($arg), $this->lang->condKeywords['left']))
+ {
+ $left = array_shift($args);
+ }
+ elseif(in_array(strtolower($arg), $this->lang->condKeywords['consumed']))
+ {
+ $consumed = array_shift($args);
+ }
+ elseif(in_array(strtolower($arg), $this->lang->condKeywords['realStarted']))
+ {
+ $realStarted = array_shift($args);
+ }
+ }
+
+ $task = $this->loadEntry('taskStart', 'post', array
+ (
+ 'taskID' => $taskID,
+ 'left' => $left,
+ 'realStarted' => $realStarted,
+ 'consumed' => $consumed,
+ ));
+ if($task->result and $task->result == 'fail')
+ {
+ $reply->messages[] = $task->message;
+ }
+ elseif(isset($task->error))
+ {
+ foreach($task->error as $error) $reply->messages[] = is_array($error) ? implode(',', $error): $error;
+ }
+ else
+ {
+ $reply->messages[] = sprintf($this->lang->effortRecorded, $taskID);
+ $reply->messages[] = $this->renderTaskTable(array($task));
+ }
+ }
+ }
+
+ return $reply;
+ }
+
+ /**
+ * Remove a Markdown link in the message and generate a response.
+ *
+ * @param $messageID
+ * @access public
+ * @return object
+ */
+ private function disableMarkdownLinkInMessage($messageID)
+ {
+ if(!$messageID) return null;
+ $message = $this->im->message->getList('', array($messageID));
+ $message = current($message);
+ if($message->user != 'xuanbot') return null;
+ $message->content = preg_replace('/\[(.*?)\]\(.*?\)/', '$1', $message->content);
+ $this->im->message->setMessage($message);
+
+ $userID = array_filter(explode('&', $message->cgid), function($item)
+ {
+ return is_numeric($item);
+ });
+
+ $response = new stdclass();
+ $response->result = 'success';
+ $response->method = 'messageUpdate';
+ $response->users = $userID;
+ $response->data = array($message);
+
+ return $response;
+ }
+
+ /**
+ * Used to call zentao's API.
+ *
+ * @param string $entry
+ * @param string $action
+ * @param array $params
+ * @param array $query
+ * @param string $version
+ * @access public
+ * @return object
+ */
+ public function loadEntry($entry, $action, $params = array(), $query = array(), $version = 'v1')
+ {
+ try
+ {
+ $this->im->loadModel('user');
+
+ $user = $this->im->user->getByID($this->im->app->input['userID'], 'id');
+ /* Authorize him and save to session. */
+ $user->rights = $this->im->user->authorize($user->account);
+ $user->groups = $this->im->user->getGroups($user->account);
+ $user->view = $this->im->user->grantUserView($user->account, $user->rights['acls'], $user->rights['projects']);
+ $user->admin = strpos($this->im->app->company->admins, ",{$user->account},") !== false;
+
+ global $app;
+ $app->action = $action;
+ $app->user = $user;
+
+ include_once($this->im->app->appRoot . "framework/api/entry.class.php");
+ include_once($this->im->app->appRoot . "api/{$version}/entries/" . strtolower($entry) . ".php");
+
+ $entryName = $entry . 'Entry';
+ $entry = new $entryName();
+
+ if($action == 'post' || $action == 'put')
+ {
+ $entry->requestBody = (object)$params;
+ }
+ elseif($action == 'get' && !empty($query))
+ {
+ $entry->setParam($query);
+ }
+
+ $content = call_user_func_array(array($entry, $action), $params);
+ return json_decode($content);
+ }
+ catch(EndResponseException $endResponseException)
+ {
+ return $endResponseException->getContent();
+ }
+ }
+}
diff --git a/xuanxuan/extension/xuan/im/ext/lang/de/xuanxuan.php b/xuanxuan/extension/xuan/im/ext/lang/de/xuanxuan.php
index cde654dd0b..b93b31219f 100644
--- a/xuanxuan/extension/xuan/im/ext/lang/de/xuanxuan.php
+++ b/xuanxuan/extension/xuan/im/ext/lang/de/xuanxuan.php
@@ -36,3 +36,89 @@ $lang->im->xxd->aes = 'Server-side AES';
$lang->im->xxdAESTip = 'This only affects server-side AES encryption between XXB and XXD.';
$lang->im->aesOptions['on'] = 'Enabled';
$lang->im->aesOptions['off'] = 'Disabled';
+
+$lang->im->bot->zentaoBot = new stdclass();
+$lang->im->bot->zentaoBot->name = 'ZenTao';
+$lang->im->bot->zentaoBot->pageSearchRegex = '/(pageID|recPerPage)=(\d+)/';
+
+$lang->im->bot->zentaoBot->commands = new stdclass();
+$lang->im->bot->zentaoBot->commands->view = new stdclass();
+$lang->im->bot->zentaoBot->commands->view->description = 'View task';
+$lang->im->bot->zentaoBot->commands->start = new stdclass();
+$lang->im->bot->zentaoBot->commands->start->description = 'Start task';
+$lang->im->bot->zentaoBot->commands->close = new stdclass();
+$lang->im->bot->zentaoBot->commands->close->description = 'Close task';
+$lang->im->bot->zentaoBot->commands->finish = new stdclass();
+$lang->im->bot->zentaoBot->commands->finish->description = 'Finish task';
+
+$lang->im->bot->zentaoBot->condKeywords = array();
+$lang->im->bot->zentaoBot->condKeywords['task'] = array('task');
+$lang->im->bot->zentaoBot->condKeywords['pri'] = array('pri');
+$lang->im->bot->zentaoBot->condKeywords['status'] = array('status');
+$lang->im->bot->zentaoBot->condKeywords['assignTo'] = array('assignto', 'user');
+$lang->im->bot->zentaoBot->condKeywords['id'] = array('id');
+$lang->im->bot->zentaoBot->condKeywords['taskName'] = array('taskname');
+$lang->im->bot->zentaoBot->condKeywords['comment'] = array('comment');
+$lang->im->bot->zentaoBot->condKeywords['left'] = array('left');
+$lang->im->bot->zentaoBot->condKeywords['consumed'] = array('consumed');
+$lang->im->bot->zentaoBot->condKeywords['realStarted'] = array('realStarted');
+$lang->im->bot->zentaoBot->condKeywords['pageID'] = array('pageID');
+$lang->im->bot->zentaoBot->condKeywords['recPerPage'] = array('recPerPage');
+$lang->im->bot->zentaoBot->condKeywords['finishedDate'] = array('finishedDate');
+$lang->im->bot->zentaoBot->condKeywords['currentConsumed'] = array('currentConsumed');
+
+$lang->im->bot->zentaoBot->success = 'Command executed successfully.';
+$lang->im->bot->zentaoBot->tasksFound = 'Found %d tasks.';
+$lang->im->bot->zentaoBot->prevPage = 'Prev Page';
+$lang->im->bot->zentaoBot->nextPage = 'Next Page';
+$lang->im->bot->zentaoBot->effortRecorded = 'Effort recorded for task #%d.';
+
+$lang->im->bot->zentaoBot->finishCommand = 'finish';
+$lang->im->bot->zentaoBot->closeCommand = 'close';
+$lang->im->bot->zentaoBot->startCommand = 'start';
+$lang->im->bot->zentaoBot->viewCommand = 'view';
+
+$lang->im->bot->zentaoBot->errors = new stdclass();
+$lang->im->bot->zentaoBot->errors->emptyResult = 'No task found.';
+$lang->im->bot->zentaoBot->errors->invalidCommand = 'Invalid command.';
+$lang->im->bot->zentaoBot->errors->invalidStatus = 'Cannot perform such action on task with %s status.';
+$lang->im->bot->zentaoBot->errors->unauthorized = 'You are not authorized to perform this action.';
+$lang->im->bot->zentaoBot->errors->taskIDRequired = 'Task ID is required.';
+$lang->im->bot->zentaoBot->errors->taskNotFound = 'Task not found.';
+
+$lang->im->bot->zentaoBot->finish = new stdclass();
+$lang->im->bot->zentaoBot->finish->tip = 'Click the link below to finish the task. Time consumed and starting time of this task are required.';
+$lang->im->bot->zentaoBot->finish->tipLinkTitle = 'Finish Task';
+$lang->im->bot->zentaoBot->finish->done = 'Task #%d is finished, ended at:%s, time consumed: %.1f hours.';
+$lang->im->bot->zentaoBot->finish->bugTip = 'Task #%d is associated with a bug, you may mark the bug as resolved by clicking the link below.';
+$lang->im->bot->zentaoBot->finish->bugTipLinkTitle = 'Resolve Bug';
+
+$lang->im->bot->zentaoBot->start = new stdclass();
+$lang->im->bot->zentaoBot->start->tip = 'Click the link below to start task #%d.';
+$lang->im->bot->zentaoBot->start->tipLinkTitle = 'Start Task';
+$lang->im->bot->zentaoBot->start->finishWithZeroLeft = 'Hours left is 0 thus the task is finished.';
+
+$lang->im->bot->zentaoBot->help = <<im->xxd->aes = 'Server-side AES';
$lang->im->xxdAESTip = 'This only affects server-side AES encryption between XXB and XXD.';
$lang->im->aesOptions['on'] = 'Enabled';
$lang->im->aesOptions['off'] = 'Disabled';
+
+$lang->im->bot->zentaoBot = new stdclass();
+$lang->im->bot->zentaoBot->name = 'ZenTao';
+$lang->im->bot->zentaoBot->pageSearchRegex = '/(pageID|recPerPage)=(\d+)/';
+
+$lang->im->bot->zentaoBot->commands = new stdclass();
+$lang->im->bot->zentaoBot->commands->view = new stdclass();
+$lang->im->bot->zentaoBot->commands->view->description = 'View task';
+$lang->im->bot->zentaoBot->commands->start = new stdclass();
+$lang->im->bot->zentaoBot->commands->start->description = 'Start task';
+$lang->im->bot->zentaoBot->commands->close = new stdclass();
+$lang->im->bot->zentaoBot->commands->close->description = 'Close task';
+$lang->im->bot->zentaoBot->commands->finish = new stdclass();
+$lang->im->bot->zentaoBot->commands->finish->description = 'Finish task';
+
+$lang->im->bot->zentaoBot->condKeywords = array();
+$lang->im->bot->zentaoBot->condKeywords['task'] = array('task');
+$lang->im->bot->zentaoBot->condKeywords['pri'] = array('pri');
+$lang->im->bot->zentaoBot->condKeywords['status'] = array('status');
+$lang->im->bot->zentaoBot->condKeywords['assignTo'] = array('assignto', 'user');
+$lang->im->bot->zentaoBot->condKeywords['id'] = array('id');
+$lang->im->bot->zentaoBot->condKeywords['taskName'] = array('taskname');
+$lang->im->bot->zentaoBot->condKeywords['comment'] = array('comment');
+$lang->im->bot->zentaoBot->condKeywords['left'] = array('left');
+$lang->im->bot->zentaoBot->condKeywords['consumed'] = array('consumed');
+$lang->im->bot->zentaoBot->condKeywords['realStarted'] = array('realStarted');
+$lang->im->bot->zentaoBot->condKeywords['pageID'] = array('pageID');
+$lang->im->bot->zentaoBot->condKeywords['recPerPage'] = array('recPerPage');
+$lang->im->bot->zentaoBot->condKeywords['finishedDate'] = array('finishedDate');
+$lang->im->bot->zentaoBot->condKeywords['currentConsumed'] = array('currentConsumed');
+
+$lang->im->bot->zentaoBot->success = 'Command executed successfully.';
+$lang->im->bot->zentaoBot->tasksFound = 'Found %d tasks.';
+$lang->im->bot->zentaoBot->prevPage = 'Prev Page';
+$lang->im->bot->zentaoBot->nextPage = 'Next Page';
+$lang->im->bot->zentaoBot->effortRecorded = 'Effort recorded for task #%d.';
+
+$lang->im->bot->zentaoBot->finishCommand = 'finish';
+$lang->im->bot->zentaoBot->closeCommand = 'close';
+$lang->im->bot->zentaoBot->startCommand = 'start';
+$lang->im->bot->zentaoBot->viewCommand = 'view';
+
+$lang->im->bot->zentaoBot->errors = new stdclass();
+$lang->im->bot->zentaoBot->errors->emptyResult = 'No task found.';
+$lang->im->bot->zentaoBot->errors->invalidCommand = 'Invalid command.';
+$lang->im->bot->zentaoBot->errors->invalidStatus = 'Cannot perform such action on task with %s status.';
+$lang->im->bot->zentaoBot->errors->unauthorized = 'You are not authorized to perform this action.';
+$lang->im->bot->zentaoBot->errors->taskIDRequired = 'Task ID is required.';
+$lang->im->bot->zentaoBot->errors->taskNotFound = 'Task not found.';
+
+$lang->im->bot->zentaoBot->finish = new stdclass();
+$lang->im->bot->zentaoBot->finish->tip = 'Click the link below to finish the task. Time consumed and starting time of this task are required.';
+$lang->im->bot->zentaoBot->finish->tipLinkTitle = 'Finish Task';
+$lang->im->bot->zentaoBot->finish->done = 'Task #%d is finished, ended at:%s, time consumed: %.1f hours.';
+$lang->im->bot->zentaoBot->finish->bugTip = 'Task #%d is associated with a bug, you may mark the bug as resolved by clicking the link below.';
+$lang->im->bot->zentaoBot->finish->bugTipLinkTitle = 'Resolve Bug';
+
+$lang->im->bot->zentaoBot->start = new stdclass();
+$lang->im->bot->zentaoBot->start->tip = 'Click the link below to start task #%d.';
+$lang->im->bot->zentaoBot->start->tipLinkTitle = 'Start Task';
+$lang->im->bot->zentaoBot->start->finishWithZeroLeft = 'Hours left is 0 thus the task is finished.';
+
+$lang->im->bot->zentaoBot->help = <<im->xxd->aes = 'Server-side AES';
$lang->im->xxdAESTip = 'This only affects server-side AES encryption between XXB and XXD.';
$lang->im->aesOptions['on'] = 'Enabled';
$lang->im->aesOptions['off'] = 'Disabled';
+
+$lang->im->bot->zentaoBot = new stdclass();
+$lang->im->bot->zentaoBot->name = 'ZenTao';
+$lang->im->bot->zentaoBot->pageSearchRegex = '/(pageID|recPerPage)=(\d+)/';
+
+$lang->im->bot->zentaoBot->commands = new stdclass();
+$lang->im->bot->zentaoBot->commands->view = new stdclass();
+$lang->im->bot->zentaoBot->commands->view->description = 'View task';
+$lang->im->bot->zentaoBot->commands->start = new stdclass();
+$lang->im->bot->zentaoBot->commands->start->description = 'Start task';
+$lang->im->bot->zentaoBot->commands->close = new stdclass();
+$lang->im->bot->zentaoBot->commands->close->description = 'Close task';
+$lang->im->bot->zentaoBot->commands->finish = new stdclass();
+$lang->im->bot->zentaoBot->commands->finish->description = 'Finish task';
+
+$lang->im->bot->zentaoBot->condKeywords = array();
+$lang->im->bot->zentaoBot->condKeywords['task'] = array('task');
+$lang->im->bot->zentaoBot->condKeywords['pri'] = array('pri');
+$lang->im->bot->zentaoBot->condKeywords['status'] = array('status');
+$lang->im->bot->zentaoBot->condKeywords['assignTo'] = array('assignto', 'user');
+$lang->im->bot->zentaoBot->condKeywords['id'] = array('id');
+$lang->im->bot->zentaoBot->condKeywords['taskName'] = array('taskname');
+$lang->im->bot->zentaoBot->condKeywords['comment'] = array('comment');
+$lang->im->bot->zentaoBot->condKeywords['left'] = array('left');
+$lang->im->bot->zentaoBot->condKeywords['consumed'] = array('consumed');
+$lang->im->bot->zentaoBot->condKeywords['realStarted'] = array('realStarted');
+$lang->im->bot->zentaoBot->condKeywords['pageID'] = array('pageID');
+$lang->im->bot->zentaoBot->condKeywords['recPerPage'] = array('recPerPage');
+$lang->im->bot->zentaoBot->condKeywords['finishedDate'] = array('finishedDate');
+$lang->im->bot->zentaoBot->condKeywords['currentConsumed'] = array('currentConsumed');
+
+$lang->im->bot->zentaoBot->success = 'Command executed successfully.';
+$lang->im->bot->zentaoBot->tasksFound = 'Found %d tasks.';
+$lang->im->bot->zentaoBot->prevPage = 'Prev Page';
+$lang->im->bot->zentaoBot->nextPage = 'Next Page';
+$lang->im->bot->zentaoBot->effortRecorded = 'Effort recorded for task #%d.';
+
+$lang->im->bot->zentaoBot->finishCommand = 'finish';
+$lang->im->bot->zentaoBot->closeCommand = 'close';
+$lang->im->bot->zentaoBot->startCommand = 'start';
+$lang->im->bot->zentaoBot->viewCommand = 'view';
+
+$lang->im->bot->zentaoBot->errors = new stdclass();
+$lang->im->bot->zentaoBot->errors->emptyResult = 'No task found.';
+$lang->im->bot->zentaoBot->errors->invalidCommand = 'Invalid command.';
+$lang->im->bot->zentaoBot->errors->invalidStatus = 'Cannot perform such action on task with %s status.';
+$lang->im->bot->zentaoBot->errors->unauthorized = 'You are not authorized to perform this action.';
+$lang->im->bot->zentaoBot->errors->taskIDRequired = 'Task ID is required.';
+$lang->im->bot->zentaoBot->errors->taskNotFound = 'Task not found.';
+
+$lang->im->bot->zentaoBot->finish = new stdclass();
+$lang->im->bot->zentaoBot->finish->tip = 'Click the link below to finish the task. Time consumed and starting time of this task are required.';
+$lang->im->bot->zentaoBot->finish->tipLinkTitle = 'Finish Task';
+$lang->im->bot->zentaoBot->finish->done = 'Task #%d is finished, ended at:%s, time consumed: %.1f hours.';
+$lang->im->bot->zentaoBot->finish->bugTip = 'Task #%d is associated with a bug, you may mark the bug as resolved by clicking the link below.';
+$lang->im->bot->zentaoBot->finish->bugTipLinkTitle = 'Resolve Bug';
+
+$lang->im->bot->zentaoBot->start = new stdclass();
+$lang->im->bot->zentaoBot->start->tip = 'Click the link below to start task #%d.';
+$lang->im->bot->zentaoBot->start->tipLinkTitle = 'Start Task';
+$lang->im->bot->zentaoBot->start->finishWithZeroLeft = 'Hours left is 0 thus the task is finished.';
+
+$lang->im->bot->zentaoBot->help = <<im->xxd->aes = '服务端通信 AES';
$lang->im->xxdAESTip = '该设置仅针对 XXB 和 XXD 之间的通讯加密,不影响客户端通讯加密。';
$lang->im->aesOptions['on'] = '开启';
$lang->im->aesOptions['off'] = '关闭';
+
+$lang->im->bot->commonName = '阿道';
+$lang->im->bot->welcome->title = '哈喽~我是你的助手阿道';
+$lang->im->bot->upgradeWelcome->title = '哈喽~我是你的助手阿道';
+
+$lang->im->bot->zentaoBot = new stdclass();
+$lang->im->bot->zentaoBot->name = '禅道';
+$lang->im->bot->zentaoBot->pageSearchRegex = '/(pageID|recPerPage|页码|每页数量|頁碼|每頁數量)=(\d+)/';
+
+$lang->im->bot->zentaoBot->commands = new stdclass();
+$lang->im->bot->zentaoBot->commands->view = new stdclass();
+$lang->im->bot->zentaoBot->commands->view->description = '查看任务';
+$lang->im->bot->zentaoBot->commands->view->alias = array('查看', '搜索', '查询', '筛选');
+$lang->im->bot->zentaoBot->commands->start = new stdclass();
+$lang->im->bot->zentaoBot->commands->start->description = '开始任务';
+$lang->im->bot->zentaoBot->commands->start->alias = array('开始', '开始任务');
+$lang->im->bot->zentaoBot->commands->close = new stdclass();
+$lang->im->bot->zentaoBot->commands->close->description = '关闭任务';
+$lang->im->bot->zentaoBot->commands->close->alias = array('关闭', '关闭任务');
+$lang->im->bot->zentaoBot->commands->finish = new stdclass();
+$lang->im->bot->zentaoBot->commands->finish->description = '完成任务';
+$lang->im->bot->zentaoBot->commands->finish->alias = array('完成', '完成任务');
+
+$lang->im->bot->zentaoBot->condKeywords = array();
+$lang->im->bot->zentaoBot->condKeywords['task'] = array('任务', 'task');
+$lang->im->bot->zentaoBot->condKeywords['pri'] = array('优先级', 'pri');
+$lang->im->bot->zentaoBot->condKeywords['status'] = array('状态', 'status');
+$lang->im->bot->zentaoBot->condKeywords['assignTo'] = array('指派人', '指派给', 'assignto', 'user');
+$lang->im->bot->zentaoBot->condKeywords['id'] = array('编号', 'id');
+$lang->im->bot->zentaoBot->condKeywords['taskName'] = array('任务名', '任务名称', 'taskname');
+$lang->im->bot->zentaoBot->condKeywords['comment'] = array('备注', 'comment');
+$lang->im->bot->zentaoBot->condKeywords['left'] = array('预计剩余', 'left');
+$lang->im->bot->zentaoBot->condKeywords['consumed'] = array('总计消耗', 'consumed');
+$lang->im->bot->zentaoBot->condKeywords['realStarted'] = array('实际开始', 'realStarted');
+$lang->im->bot->zentaoBot->condKeywords['pageID'] = array('pageID', '页码', '頁碼');
+$lang->im->bot->zentaoBot->condKeywords['recPerPage'] = array('recPerPage', '每页数量', '每頁數量');
+$lang->im->bot->zentaoBot->condKeywords['finishedDate'] = array('实际完成', 'finishedDate');
+$lang->im->bot->zentaoBot->condKeywords['currentConsumed'] = array('本次消耗', 'currentConsumed');
+
+$lang->im->bot->zentaoBot->success = '指令执行完成';
+$lang->im->bot->zentaoBot->tasksFound = '为您匹配到 %d 项任务';
+$lang->im->bot->zentaoBot->prevPage = '上一页';
+$lang->im->bot->zentaoBot->nextPage = '下一页';
+$lang->im->bot->zentaoBot->effortRecorded = '任务 #%d 已完成工时信息填写';
+
+$lang->im->bot->zentaoBot->finishCommand = '完成';
+$lang->im->bot->zentaoBot->closeCommand = '关闭';
+$lang->im->bot->zentaoBot->startCommand = '开始';
+$lang->im->bot->zentaoBot->viewCommand = '查看';
+
+$lang->im->bot->zentaoBot->errors = new stdclass();
+$lang->im->bot->zentaoBot->errors->emptyResult = '未查询到相关匹配信息';
+$lang->im->bot->zentaoBot->errors->invalidCommand = '无法识别该指令';
+$lang->im->bot->zentaoBot->errors->invalidStatus = '检测到该任务为%s状态,无法实现指令操作';
+$lang->im->bot->zentaoBot->errors->unauthorized = '您无权操作此任务';
+$lang->im->bot->zentaoBot->errors->taskIDRequired = '请输入任务编号';
+$lang->im->bot->zentaoBot->errors->taskNotFound = '任务不存在';
+
+$lang->im->bot->zentaoBot->finish = new stdclass();
+$lang->im->bot->zentaoBot->finish->tip = '完成任务指令需要填入工时与记录起始时间,请点击下方入口';
+$lang->im->bot->zentaoBot->finish->tipLinkTitle = '工时记录';
+$lang->im->bot->zentaoBot->finish->done = '任务 #%d 已完成,完成时间:%s,消耗:%.1f 小时';
+$lang->im->bot->zentaoBot->finish->bugTip = '检测到任务 #%d 关联相关 Bug,您可以点击以下链接进行处理';
+$lang->im->bot->zentaoBot->finish->bugTipLinkTitle = '关联 Bug 处理';
+
+$lang->im->bot->zentaoBot->start = new stdclass();
+$lang->im->bot->zentaoBot->start->tip = '点击链接开始任务 #%d';
+$lang->im->bot->zentaoBot->start->tipLinkTitle = '开始任务';
+$lang->im->bot->zentaoBot->start->finishWithZeroLeft = '剩余工时为 0,任务将被标记为"已完成"';
+
+$lang->im->bot->zentaoBot->help = <<