diff --git a/config/zentaopms.php b/config/zentaopms.php
index a66441e4e2..76822302e5 100644
--- a/config/zentaopms.php
+++ b/config/zentaopms.php
@@ -167,6 +167,7 @@ define('TABLE_USERTPL', '`' . $config->db->prefix . 'usertpl`');
define('TABLE_PRODUCT', '`' . $config->db->prefix . 'product`');
define('TABLE_BRANCH', '`' . $config->db->prefix . 'branch`');
define('TABLE_EXPECT', '`' . $config->db->prefix . 'expect`');
+define('TABLE_STAGE', '`' . $config->db->prefix . 'stage`');
define('TABLE_STAKEHOLDER', '`' . $config->db->prefix . 'stakeholder`');
define('TABLE_STORY', '`' . $config->db->prefix . 'story`');
define('TABLE_STORYSPEC', '`' . $config->db->prefix . 'storyspec`');
@@ -192,6 +193,8 @@ define('TABLE_BURN', '`' . $config->db->prefix . 'burn`');
define('TABLE_BUILD', '`' . $config->db->prefix . 'build`');
define('TABLE_ACL', '`' . $config->db->prefix . 'acl`');
+define('TABLE_DESIGN', '`' . $config->db->prefix . 'design`');
+define('TABLE_DESIGNSPEC', '`' . $config->db->prefix . 'designspec`');
define('TABLE_DOCLIB', '`' . $config->db->prefix . 'doclib`');
define('TABLE_DOC', '`' . $config->db->prefix . 'doc`');
define('TABLE_API', '`' . $config->db->prefix . 'api`');
diff --git a/db/update15.9.sql b/db/update15.9.sql
new file mode 100644
index 0000000000..c9a0fdbcb4
--- /dev/null
+++ b/db/update15.9.sql
@@ -0,0 +1,48 @@
+-- DROP TABLE IF EXISTS `zt_stage`;
+CREATE TABLE IF NOT EXISTS `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=MyISAM DEFAULT CHARSET=utf8;
+
+-- DROP TABLE IF EXISTS `zt_design`;
+CREATE TABLE IF NOT EXISTS `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` text NOT NULL,
+ `version` smallint(6) NOT NULL,
+ `type` char(30) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- DROP TABLE IF EXISTS `zt_designspec`;
+CREATE TABLE IF NOT EXISTS `zt_designspec` (
+ `design` mediumint(8) NOT NULL,
+ `version` smallint(6) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `desc` text NOT NULL,
+ `files` varchar(255) NOT NULL,
+ UNIQUE KEY `design` (`design`,`version`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
diff --git a/db/zentao.sql b/db/zentao.sql
index 60f013c5d7..4a5a29f530 100644
--- a/db/zentao.sql
+++ b/db/zentao.sql
@@ -30,99 +30,94 @@ CREATE TABLE IF NOT EXISTS `zt_action` (
KEY `objectID` (`objectID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_api_lib_release`;
-CREATE TABLE `zt_api_lib_release`
-(
- `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
- `lib` int 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`)
+CREATE TABLE `zt_api_lib_release` (
+ `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
+ `lib` int 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=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_api`;
-CREATE TABLE `zt_api`
-(
- `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
- `product` varchar(255) NOT NULL DEFAULT '',
- `lib` int UNSIGNED NOT NULL DEFAULT 0,
- `module` int 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` text NULL,
- `version` smallint UNSIGNED NOT NULL DEFAULT 0,
- `params` text NULL,
- `paramsExample` text NUll,
- `responseExample` text NUll,
- `response` text NULL,
- `commonParams` text NULL,
- `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`)
+CREATE TABLE `zt_api` (
+ `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
+ `product` varchar(255) NOT NULL DEFAULT '',
+ `lib` int UNSIGNED NOT NULL DEFAULT 0,
+ `module` int 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` text NULL,
+ `version` smallint UNSIGNED NOT NULL DEFAULT 0,
+ `params` text NULL,
+ `paramsExample` text NUll,
+ `responseExample` text NUll,
+ `response` text NULL,
+ `commonParams` text NULL,
+ `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=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_apispec`;
-CREATE TABLE `zt_apispec`
-(
- `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
- `doc` int UNSIGNED NOT NULL DEFAULT 0,
- `module` int 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` text NULL,
- `version` smallint UNSIGNED NOT NULL DEFAULT 0,
- `params` text NULL,
- `paramsExample` text NUll,
- `responseExample` text NUll,
- `response` text NULL,
- `addedBy` varchar(30) NOT NULL DEFAULT 0,
- `addedDate` datetime NULL,
- PRIMARY KEY (`id`)
+CREATE TABLE `zt_apispec` (
+ `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
+ `doc` int UNSIGNED NOT NULL DEFAULT 0,
+ `module` int 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` text NULL,
+ `version` smallint UNSIGNED NOT NULL DEFAULT 0,
+ `params` text NULL,
+ `paramsExample` text NUll,
+ `responseExample` text NUll,
+ `response` text NULL,
+ `addedBy` varchar(30) NOT NULL DEFAULT 0,
+ `addedDate` datetime NULL,
+ PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_apistruct`;
-CREATE TABLE `zt_apistruct`
-(
- `id` int unsigned NOT NULL AUTO_INCREMENT,
- `lib` int UNSIGNED NOT NULL DEFAULT 0,
- `name` varchar(30) NOT NULL DEFAULT '',
- `type` varchar(50) NOT NULL DEFAULT '',
- `desc` varchar(255) NOT NULL DEFAULT '',
- `version` smallint unsigned NOT NULL DEFAULT 0,
- `attribute` text NULL,
- `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`)
+CREATE TABLE `zt_apistruct` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
+ `lib` int UNSIGNED NOT NULL DEFAULT 0,
+ `name` varchar(30) NOT NULL DEFAULT '',
+ `type` varchar(50) NOT NULL DEFAULT '',
+ `desc` varchar(255) NOT NULL DEFAULT '',
+ `version` smallint unsigned NOT NULL DEFAULT 0,
+ `attribute` text NULL,
+ `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=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_apistruct_spec`;
-CREATE TABLE `zt_apistruct_spec`
-(
- `id` int 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 NULL,
- `version` smallint unsigned NOT NULL DEFAULT 0,
- `addedBy` varchar(30) NOT NULL DEFAULT 0,
- `addedDate` datetime NOT NULL,
- primary key (`id`)
+CREATE TABLE `zt_apistruct_spec` (
+ `id` int 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 NULL,
+ `version` smallint unsigned NOT NULL DEFAULT 0,
+ `addedBy` varchar(30) NOT NULL DEFAULT 0,
+ `addedDate` datetime NOT NULL,
+ primary key (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_block`;
CREATE TABLE IF NOT EXISTS `zt_block` (
@@ -393,6 +388,39 @@ CREATE TABLE IF NOT EXISTS `zt_dept` (
KEY `parent` (`parent`),
KEY `path` (`path`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+-- DROP TABLE IF EXISTS `zt_design`;
+CREATE TABLE IF NOT EXISTS `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` text NOT NULL,
+ `version` smallint(6) NOT NULL,
+ `type` char(30) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+-- DROP TABLE IF EXISTS `zt_designspec`;
+CREATE TABLE IF NOT EXISTS `zt_designspec` (
+ `design` mediumint(8) NOT NULL,
+ `version` smallint(6) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `desc` text NOT NULL,
+ `files` varchar(255) NOT NULL,
+ UNIQUE KEY `design` (`design`,`version`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_doc`;
CREATE TABLE IF NOT EXISTS `zt_doc` (
`id` mediumint(8) unsigned NOT NULL auto_increment,
@@ -1016,6 +1044,19 @@ CREATE TABLE IF NOT EXISTS `zt_searchindex` (
FULLTEXT KEY `content` (`content`),
FULLTEXT KEY `title` (`title`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+-- DROP TABLE IF EXISTS `zt_stage`;
+CREATE TABLE IF NOT EXISTS `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=MyISAM DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_stakeholder`;
CREATE TABLE IF NOT EXISTS `zt_stakeholder` (
`id` mediumint(8) NOT NULL AUTO_INCREMENT PRIMARY KEY,
diff --git a/module/common/lang/common.php b/module/common/lang/common.php
index ee993c8a48..96f8e12f14 100644
--- a/module/common/lang/common.php
+++ b/module/common/lang/common.php
@@ -6,6 +6,8 @@ $lang->todo = new stdclass();
$lang->program = new stdclass();
$lang->product = new stdclass();
$lang->project = new stdclass();
+$lang->design = new stdclass();
+$lang->stage = new stdclass();
$lang->scrum = new stdclass();
$lang->waterfall = new stdclass();
$lang->execution = new stdclass();
diff --git a/module/common/lang/en.php b/module/common/lang/en.php
index 84156def3d..5c105d49bb 100644
--- a/module/common/lang/en.php
+++ b/module/common/lang/en.php
@@ -44,35 +44,38 @@ $lang->runInfo = "
- config->maxVersion)):?>
' . $lang->project->create, '', 'class="btn btn-primary" data-toggle="modal" data-target="#guideDialog"');?>
- config->systemMode == 'new'):?>
- ' . $lang->project->create, '', 'class="btn btn-primary"');?>
-
@@ -43,11 +39,7 @@ js::set('browseType', $browseType);
project->empty;?>
- config->maxVersion)):?>
' . $lang->project->create, '', 'class="btn btn-info btn-wide " data-toggle="modal" data-target="#guideDialog"');?>
- config->systemMode == 'new'):?>
- ' . $lang->project->create, '', 'class="btn btn-info btn-wide"');?>
-
diff --git a/module/programplan/config.php b/module/programplan/config.php
new file mode 100644
index 0000000000..2710d95c98
--- /dev/null
+++ b/module/programplan/config.php
@@ -0,0 +1,54 @@
+programplan->create = new stdclass();
+$config->programplan->edit = new stdclass();
+$config->programplan->create->requiredFields = 'name,begin,end';
+$config->programplan->edit->requiredFields = 'name,begin,end';
+
+$config->programplan->datatable = new stdclass();
+$config->programplan->datatable->defaultField = array('id', 'name', 'percent', 'attribute', 'begin', 'end', 'realBegan', 'realEnd', 'actions');
+
+$config->programplan->datatable->fieldList['id']['title'] = 'idAB';
+$config->programplan->datatable->fieldList['id']['fixed'] = 'left';
+$config->programplan->datatable->fieldList['id']['width'] = '70';
+$config->programplan->datatable->fieldList['id']['required'] = 'yes';
+
+$config->programplan->datatable->fieldList['name']['title'] = 'name';
+$config->programplan->datatable->fieldList['name']['fixed'] = 'left';
+$config->programplan->datatable->fieldList['name']['width'] = 'auto';
+$config->programplan->datatable->fieldList['name']['required'] = 'yes';
+
+$config->programplan->datatable->fieldList['percent']['title'] = 'percent';
+$config->programplan->datatable->fieldList['percent']['fixed'] = 'no';
+$config->programplan->datatable->fieldList['percent']['width'] = '100';
+$config->programplan->datatable->fieldList['percent']['required'] = 'no';
+
+$config->programplan->datatable->fieldList['attribute']['title'] = 'attribute';
+$config->programplan->datatable->fieldList['attribute']['fixed'] = 'no';
+$config->programplan->datatable->fieldList['attribute']['width'] = '90';
+$config->programplan->datatable->fieldList['attribute']['required'] = 'no';
+
+$config->programplan->datatable->fieldList['begin']['title'] = 'begin';
+$config->programplan->datatable->fieldList['begin']['fixed'] = 'no';
+$config->programplan->datatable->fieldList['begin']['width'] = '90';
+$config->programplan->datatable->fieldList['begin']['required'] = 'no';
+
+$config->programplan->datatable->fieldList['end']['title'] = 'end';
+$config->programplan->datatable->fieldList['end']['fixed'] = 'no';
+$config->programplan->datatable->fieldList['end']['width'] = '90';
+$config->programplan->datatable->fieldList['end']['required'] = 'no';
+
+$config->programplan->datatable->fieldList['realBegan']['title'] = 'realBegan';
+$config->programplan->datatable->fieldList['realBegan']['fixed'] = 'no';
+$config->programplan->datatable->fieldList['realBegan']['width'] = '90';
+$config->programplan->datatable->fieldList['realBegan']['required'] = 'no';
+
+$config->programplan->datatable->fieldList['realEnd']['title'] = 'realEnd';
+$config->programplan->datatable->fieldList['realEnd']['fixed'] = 'no';
+$config->programplan->datatable->fieldList['realEnd']['width'] = '90';
+$config->programplan->datatable->fieldList['realEnd']['required'] = 'no';
+
+$config->programplan->datatable->fieldList['actions']['title'] = 'actions';
+$config->programplan->datatable->fieldList['actions']['fixed'] = 'right';
+$config->programplan->datatable->fieldList['actions']['width'] = '150';
+$config->programplan->datatable->fieldList['actions']['required'] = 'yes';
+$config->programplan->datatable->fieldList['actions']['sort'] = 'no';
diff --git a/module/programplan/control.php b/module/programplan/control.php
new file mode 100644
index 0000000000..f261191566
--- /dev/null
+++ b/module/programplan/control.php
@@ -0,0 +1,201 @@
+
+ * @package programplan
+ * @version $Id: control.php 5107 2013-07-12 01:46:12Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+class programplan extends control
+{
+ /**
+ * __construct
+ *
+ * @param string $moduleName
+ * @param string $methodName
+ * @access public
+ * @return void
+ */
+ public function __construct($moduleName = '', $methodName = '')
+ {
+ parent::__construct($moduleName, $methodName);
+ }
+
+ /**
+ * Common action.
+ *
+ * @param int $projectID
+ * @param int $productID
+ * @param string $extra
+ * @access public
+ * @return void
+ */
+ public function commonAction($projectID, $productID = 0, $extra = '')
+ {
+ $products = $this->loadModel('product')->getProductPairsByProject($projectID);
+ $productID = $this->product->saveState($productID, $products);
+
+ $this->productID = $productID;
+ $this->loadModel('project')->setMenu($projectID);
+ }
+
+ /**
+ * Browse program plans.
+ *
+ * @param int $projectID
+ * @param int $productID
+ * @param string $type
+ * @param string $orderBy
+ * @param int $baselineID
+ * @access public
+ * @return void
+ */
+ public function browse($projectID = 0, $productID = 0, $type = 'gantt', $orderBy = 'id_asc', $baselineID = 0)
+ {
+ $this->app->loadLang('stage');
+ $this->commonAction($projectID, $productID, $type);
+ $this->session->set('projectPlanList', $this->app->getURI(true), 'project');
+
+ if(!defined('RUN_MODE') || RUN_MODE != 'api') $projectID = $this->project->saveState((int)$projectID, $this->project->getPairsByProgram());
+
+ $products = $this->project->getProducts($projectID);
+ $this->lang->modulePageNav = $this->product->select($products, $this->productID, 'programplan', 'browse', $type, 0, 0, '', false);
+
+ if(common::hasPriv('programplan', 'create')) $this->lang->TRActions = html::a($this->createLink('programplan', 'create', "projectID=$projectID"), "
" . $this->lang->programplan->create, '', "class='btn btn-primary'");
+
+ $selectCustom = 0; // Display date and task settings.
+ $dateDetails = 1; // Gantt chart detail date display.
+ if($type == 'gantt')
+ {
+ $owner = $this->app->user->account;
+ $module = 'programplan';
+ $section = 'browse';
+ $object = 'stageCustom';
+ $selectCustom = $this->loadModel('setting')->getItem("owner={$owner}&module={$module}§ion={$section}&key={$object}");
+ if(strpos($selectCustom, 'date') !== false) $dateDetails = 0;
+
+ $plans = $this->programplan->getDataForGantt($projectID, $this->productID, $baselineID);
+ }
+
+ if($type == 'lists')
+ {
+ $sort = $this->loadModel('common')->appendOrder($orderBy);
+ $this->loadModel('datatable');
+ $plans = $this->programplan->getPlans($projectID, $this->productID, $sort);
+ }
+
+ $this->view->title = $this->lang->programplan->browse;
+ $this->view->position[] = $this->lang->programplan->browse;
+ $this->view->projectID = $projectID;
+ $this->view->productID = $this->productID;
+ $this->view->type = $type;
+ $this->view->plans = $plans;
+ $this->view->orderBy = $orderBy;
+ $this->view->selectCustom = $selectCustom;
+ $this->view->dateDetails = $dateDetails;
+ $this->view->users = $this->loadModel('user')->getPairs('noletter');
+
+ $this->display();
+ }
+
+ /**
+ * Create a project plan.
+ *
+ * @param int $projectID
+ * @param int $productID
+ * @param int $planID
+ * @access public
+ * @return void
+ */
+ public function create($projectID = 0, $productID = 0, $planID = 0)
+ {
+ $this->commonAction($projectID, $productID);
+ $this->app->loadLang('project');
+ if($_POST)
+ {
+ $this->programplan->create($projectID, $this->productID, $planID);
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+
+ $locate = $this->session->projectPlanList ? $this->session->projectPlanList : $this->createLink('programplan', 'browse', "projectID=$projectID");
+ return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $locate));
+ }
+
+ $this->app->loadLang('stage');
+ $project = $this->loadModel('project')->getById($projectID);
+
+ $this->view->title = $this->lang->programplan->create . $this->lang->colon . $project->name;
+ $this->view->position[] = html::a($this->createLink('programplan', 'browse', "projectID=$projectID"), $project->name);
+ $this->view->position[] = $this->lang->programplan->create;
+
+ $this->view->project = $project;
+ $this->view->stages = empty($planID) ? $this->loadModel('stage')->getStages('id_asc') : array();
+ $this->view->programPlan = $this->project->getById($planID, 'stage');
+ $this->view->plans = $this->programplan->getStage($planID ? $planID : $projectID, $this->productID, 'parent');
+ $this->view->planID = $planID;
+ $this->view->type = 'lists';
+
+ $this->display();
+ }
+
+ /**
+ * Edit a project plan.
+ *
+ * @param int $planID
+ * @param int $projectID
+ * @access public
+ * @return void
+ */
+ public function edit($planID = 0, $projectID = 0)
+ {
+ $this->app->loadLang('project');
+ $plan = $this->programplan->getByID($planID);
+ if($_POST)
+ {
+ $changes = $this->programplan->update($planID, $projectID);
+
+ if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+ if($changes)
+ {
+ $actionID = $this->loadModel('action')->create('execution', $planID, 'edited');
+ $this->action->logHistory($actionID, $changes);
+ }
+ $locate = isonlybody() ? 'parent' : inlink('browse', "program=$plan->program&type=lists");
+ return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $locate));
+ }
+
+ $this->app->loadLang('stage');
+ $this->view->title = $this->lang->programplan->edit;
+ $this->view->position[] = $this->lang->programplan->edit;
+ $this->view->parentStage = $this->programplan->getParentStageList($this->session->project, $planID, $plan->product);
+ $this->view->isCreateTask = $this->programplan->isCreateTask($planID);
+ $this->view->plan = $plan;
+
+ $this->display();
+ }
+
+ /**
+ * Save custom settings via ajax.
+ *
+ * @access public
+ * @return void
+ */
+ public function ajaxCustom()
+ {
+ $data = fixer::input('post')->get();
+ $owner = $this->app->user->account;
+ $module = 'programplan';
+ $section = 'browse';
+ $object = 'stageCustom';
+ $setting = $this->loadModel('setting');
+ $custom = empty($data->stageCustom) ? '' : implode(',', $data->stageCustom);
+ $setting->setItem("$owner.$module.$section.$object", $custom);
+
+ $response = array();
+ $response['result'] = 'success';
+ $response['message'] = '';
+ return $this->send($response);
+ }
+}
diff --git a/module/programplan/js/browse.js b/module/programplan/js/browse.js
new file mode 100644
index 0000000000..480420dc76
--- /dev/null
+++ b/module/programplan/js/browse.js
@@ -0,0 +1,34 @@
+$(function()
+{
+ setTimeout(function()
+ {
+ fixScroll();
+ }, 500);
+})
+
+function fixScroll()
+{
+ var $scrollwrapper = $('div.datatable').first().find('.scroll-wrapper:first');
+ if($scrollwrapper.size() == 0)return;
+
+ var $tfoot = $('div.datatable').first().find('table tfoot:last');
+ var scrollOffset = $scrollwrapper.offset().top + $scrollwrapper.find('.scroll-slide').height();
+ if($tfoot.size() > 0) scrollOffset += $tfoot.height();
+ if($('div.datatable.head-fixed').size() == 0) scrollOffset -= '29';
+ var windowH = $(window).height();
+ if(scrollOffset > windowH + $(window).scrollTop()) $scrollwrapper.css({'position': 'fixed', 'bottom': 50 + 'px'});
+ $(window).scroll(function()
+ {
+ newBottom = $tfoot.hasClass('fixedTfootAction') ? 50 + $tfoot.height() : 50;
+ if(typeof(ssoRedirect) != "undefined") newBottom = 50;
+ if(scrollOffset <= windowH + $(window).scrollTop())
+ {
+ $scrollwrapper.css({'position':'relative', 'bottom': '0px'});
+ }
+ else if($scrollwrapper.css('position') != 'fixed')
+ {
+ $scrollwrapper.css({'position': 'fixed', 'bottom': newBottom + 'px'});
+ bottom = newBottom;
+ }
+ });
+}
diff --git a/module/programplan/js/create.js b/module/programplan/js/create.js
new file mode 100644
index 0000000000..62a08dbd0e
--- /dev/null
+++ b/module/programplan/js/create.js
@@ -0,0 +1,16 @@
+function addItem(obj)
+{
+ var item = $('#addItem').html().replace(/%i%/g, i);
+ $(obj).closest('tr').after('
' + item + '
');
+ var newItem = $('#names' + i).closest('tr');
+ newItem.find('.form-date').datepicker();
+ $("#output" + i).chosen();
+ $("#output_i__chosen").remove();
+ i ++;
+}
+
+function deleteItem(obj)
+{
+ if($('#planForm .table tbody').children().length < 2) return false;
+ $(obj).closest('tr').remove();
+}
diff --git a/module/programplan/lang/de.php b/module/programplan/lang/de.php
new file mode 100644
index 0000000000..37a10b3e13
--- /dev/null
+++ b/module/programplan/lang/de.php
@@ -0,0 +1,73 @@
+
+ * @package programplan
+ * @version $Id: en.php 4729 2013-05-03 07:53:55Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+$lang->programplan->common = 'Program Plan';
+$lang->programplan->browse = 'Program Plan';
+$lang->programplan->gantt = 'Gantt Chart';
+$lang->programplan->list = 'Stage List';
+$lang->programplan->create = 'Create';
+$lang->programplan->edit = 'Edit';
+$lang->programplan->delete = 'Delete Stage';
+$lang->programplan->createSubPlan = 'Create Sub Plan';
+
+$lang->programplan->parent = 'Parent Stage';
+$lang->programplan->emptyParent = 'N/A';
+$lang->programplan->name = 'Stage Name';
+$lang->programplan->subStageName = 'Sub Stage Name';
+$lang->programplan->percent = 'Workload Ratio';
+$lang->programplan->percentAB = 'Workload Ratio';
+$lang->programplan->planPercent = 'Workload';
+$lang->programplan->attribute = 'Stage Type';
+$lang->programplan->milestone = 'Milestone';
+$lang->programplan->taskProgress = 'Task Progress';
+$lang->programplan->task = 'Task';
+$lang->programplan->begin = 'Begin';
+$lang->programplan->end = 'End';
+$lang->programplan->realBegan = 'Actual Started';
+$lang->programplan->realEnd = 'Actual End';
+$lang->programplan->ac = 'Actual cost';
+$lang->programplan->sv = 'Schedule Variance';
+$lang->programplan->cv = 'Cost Variance';
+$lang->programplan->planDateRange = 'Planned Start';
+$lang->programplan->realDateRange = 'Actual Start';
+$lang->programplan->output = 'Output';
+$lang->programplan->openedBy = 'Created By';
+$lang->programplan->openedDate = 'Created Date';
+$lang->programplan->editedBy = 'Edited By';
+$lang->programplan->editedDate = 'Edited Date';
+$lang->programplan->duration = 'Duration';
+$lang->programplan->version = 'Version';
+$lang->programplan->full = 'Full Screen';
+$lang->programplan->today = 'Today';
+$lang->programplan->exporting = 'Exporting';
+$lang->programplan->exportFail = 'Export failed';
+$lang->programplan->hideCriticalPath = 'Hide Critical Path';
+$lang->programplan->showCriticalPath = 'Show Critical Path';
+
+$lang->programplan->milestoneList[1] = 'Yes';
+$lang->programplan->milestoneList[0] = 'No';
+
+$lang->programplan->noData = 'No Data';
+$lang->programplan->children = 'Sub Plan';
+$lang->programplan->childrenAB = 'Child';
+$lang->programplan->confirmDelete = 'Do you want to delete the current plan?';
+$lang->programplan->workloadTips = 'The proportion of the sub stage workload is divided by 100%.';
+
+$lang->programplan->stageCustom = new stdClass();
+$lang->programplan->stageCustom->date = 'Show Date';
+$lang->programplan->stageCustom->task = 'Show Task';
+
+$lang->programplan->error = new stdclass();
+$lang->programplan->error->percentNumber = '"Workload %" must be digits.';
+$lang->programplan->error->planFinishSmall = 'The "End" date must be > the "Begin" date.';
+$lang->programplan->error->percentOver = 'The sum of "Workload %" cannot exceed 100%.';
+$lang->programplan->error->createdTask = 'The task has been decomposed. Sub phases cannot be added.';
+$lang->programplan->error->parentWorkload = 'The sum of the workload of the child phase cannot be greater than that of the parent phase: %s.';
diff --git a/module/programplan/lang/en.php b/module/programplan/lang/en.php
new file mode 100644
index 0000000000..37a10b3e13
--- /dev/null
+++ b/module/programplan/lang/en.php
@@ -0,0 +1,73 @@
+
+ * @package programplan
+ * @version $Id: en.php 4729 2013-05-03 07:53:55Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+$lang->programplan->common = 'Program Plan';
+$lang->programplan->browse = 'Program Plan';
+$lang->programplan->gantt = 'Gantt Chart';
+$lang->programplan->list = 'Stage List';
+$lang->programplan->create = 'Create';
+$lang->programplan->edit = 'Edit';
+$lang->programplan->delete = 'Delete Stage';
+$lang->programplan->createSubPlan = 'Create Sub Plan';
+
+$lang->programplan->parent = 'Parent Stage';
+$lang->programplan->emptyParent = 'N/A';
+$lang->programplan->name = 'Stage Name';
+$lang->programplan->subStageName = 'Sub Stage Name';
+$lang->programplan->percent = 'Workload Ratio';
+$lang->programplan->percentAB = 'Workload Ratio';
+$lang->programplan->planPercent = 'Workload';
+$lang->programplan->attribute = 'Stage Type';
+$lang->programplan->milestone = 'Milestone';
+$lang->programplan->taskProgress = 'Task Progress';
+$lang->programplan->task = 'Task';
+$lang->programplan->begin = 'Begin';
+$lang->programplan->end = 'End';
+$lang->programplan->realBegan = 'Actual Started';
+$lang->programplan->realEnd = 'Actual End';
+$lang->programplan->ac = 'Actual cost';
+$lang->programplan->sv = 'Schedule Variance';
+$lang->programplan->cv = 'Cost Variance';
+$lang->programplan->planDateRange = 'Planned Start';
+$lang->programplan->realDateRange = 'Actual Start';
+$lang->programplan->output = 'Output';
+$lang->programplan->openedBy = 'Created By';
+$lang->programplan->openedDate = 'Created Date';
+$lang->programplan->editedBy = 'Edited By';
+$lang->programplan->editedDate = 'Edited Date';
+$lang->programplan->duration = 'Duration';
+$lang->programplan->version = 'Version';
+$lang->programplan->full = 'Full Screen';
+$lang->programplan->today = 'Today';
+$lang->programplan->exporting = 'Exporting';
+$lang->programplan->exportFail = 'Export failed';
+$lang->programplan->hideCriticalPath = 'Hide Critical Path';
+$lang->programplan->showCriticalPath = 'Show Critical Path';
+
+$lang->programplan->milestoneList[1] = 'Yes';
+$lang->programplan->milestoneList[0] = 'No';
+
+$lang->programplan->noData = 'No Data';
+$lang->programplan->children = 'Sub Plan';
+$lang->programplan->childrenAB = 'Child';
+$lang->programplan->confirmDelete = 'Do you want to delete the current plan?';
+$lang->programplan->workloadTips = 'The proportion of the sub stage workload is divided by 100%.';
+
+$lang->programplan->stageCustom = new stdClass();
+$lang->programplan->stageCustom->date = 'Show Date';
+$lang->programplan->stageCustom->task = 'Show Task';
+
+$lang->programplan->error = new stdclass();
+$lang->programplan->error->percentNumber = '"Workload %" must be digits.';
+$lang->programplan->error->planFinishSmall = 'The "End" date must be > the "Begin" date.';
+$lang->programplan->error->percentOver = 'The sum of "Workload %" cannot exceed 100%.';
+$lang->programplan->error->createdTask = 'The task has been decomposed. Sub phases cannot be added.';
+$lang->programplan->error->parentWorkload = 'The sum of the workload of the child phase cannot be greater than that of the parent phase: %s.';
diff --git a/module/programplan/lang/fr.php b/module/programplan/lang/fr.php
new file mode 100644
index 0000000000..37a10b3e13
--- /dev/null
+++ b/module/programplan/lang/fr.php
@@ -0,0 +1,73 @@
+
+ * @package programplan
+ * @version $Id: en.php 4729 2013-05-03 07:53:55Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+$lang->programplan->common = 'Program Plan';
+$lang->programplan->browse = 'Program Plan';
+$lang->programplan->gantt = 'Gantt Chart';
+$lang->programplan->list = 'Stage List';
+$lang->programplan->create = 'Create';
+$lang->programplan->edit = 'Edit';
+$lang->programplan->delete = 'Delete Stage';
+$lang->programplan->createSubPlan = 'Create Sub Plan';
+
+$lang->programplan->parent = 'Parent Stage';
+$lang->programplan->emptyParent = 'N/A';
+$lang->programplan->name = 'Stage Name';
+$lang->programplan->subStageName = 'Sub Stage Name';
+$lang->programplan->percent = 'Workload Ratio';
+$lang->programplan->percentAB = 'Workload Ratio';
+$lang->programplan->planPercent = 'Workload';
+$lang->programplan->attribute = 'Stage Type';
+$lang->programplan->milestone = 'Milestone';
+$lang->programplan->taskProgress = 'Task Progress';
+$lang->programplan->task = 'Task';
+$lang->programplan->begin = 'Begin';
+$lang->programplan->end = 'End';
+$lang->programplan->realBegan = 'Actual Started';
+$lang->programplan->realEnd = 'Actual End';
+$lang->programplan->ac = 'Actual cost';
+$lang->programplan->sv = 'Schedule Variance';
+$lang->programplan->cv = 'Cost Variance';
+$lang->programplan->planDateRange = 'Planned Start';
+$lang->programplan->realDateRange = 'Actual Start';
+$lang->programplan->output = 'Output';
+$lang->programplan->openedBy = 'Created By';
+$lang->programplan->openedDate = 'Created Date';
+$lang->programplan->editedBy = 'Edited By';
+$lang->programplan->editedDate = 'Edited Date';
+$lang->programplan->duration = 'Duration';
+$lang->programplan->version = 'Version';
+$lang->programplan->full = 'Full Screen';
+$lang->programplan->today = 'Today';
+$lang->programplan->exporting = 'Exporting';
+$lang->programplan->exportFail = 'Export failed';
+$lang->programplan->hideCriticalPath = 'Hide Critical Path';
+$lang->programplan->showCriticalPath = 'Show Critical Path';
+
+$lang->programplan->milestoneList[1] = 'Yes';
+$lang->programplan->milestoneList[0] = 'No';
+
+$lang->programplan->noData = 'No Data';
+$lang->programplan->children = 'Sub Plan';
+$lang->programplan->childrenAB = 'Child';
+$lang->programplan->confirmDelete = 'Do you want to delete the current plan?';
+$lang->programplan->workloadTips = 'The proportion of the sub stage workload is divided by 100%.';
+
+$lang->programplan->stageCustom = new stdClass();
+$lang->programplan->stageCustom->date = 'Show Date';
+$lang->programplan->stageCustom->task = 'Show Task';
+
+$lang->programplan->error = new stdclass();
+$lang->programplan->error->percentNumber = '"Workload %" must be digits.';
+$lang->programplan->error->planFinishSmall = 'The "End" date must be > the "Begin" date.';
+$lang->programplan->error->percentOver = 'The sum of "Workload %" cannot exceed 100%.';
+$lang->programplan->error->createdTask = 'The task has been decomposed. Sub phases cannot be added.';
+$lang->programplan->error->parentWorkload = 'The sum of the workload of the child phase cannot be greater than that of the parent phase: %s.';
diff --git a/module/programplan/lang/vi.php b/module/programplan/lang/vi.php
new file mode 100644
index 0000000000..37a10b3e13
--- /dev/null
+++ b/module/programplan/lang/vi.php
@@ -0,0 +1,73 @@
+
+ * @package programplan
+ * @version $Id: en.php 4729 2013-05-03 07:53:55Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+$lang->programplan->common = 'Program Plan';
+$lang->programplan->browse = 'Program Plan';
+$lang->programplan->gantt = 'Gantt Chart';
+$lang->programplan->list = 'Stage List';
+$lang->programplan->create = 'Create';
+$lang->programplan->edit = 'Edit';
+$lang->programplan->delete = 'Delete Stage';
+$lang->programplan->createSubPlan = 'Create Sub Plan';
+
+$lang->programplan->parent = 'Parent Stage';
+$lang->programplan->emptyParent = 'N/A';
+$lang->programplan->name = 'Stage Name';
+$lang->programplan->subStageName = 'Sub Stage Name';
+$lang->programplan->percent = 'Workload Ratio';
+$lang->programplan->percentAB = 'Workload Ratio';
+$lang->programplan->planPercent = 'Workload';
+$lang->programplan->attribute = 'Stage Type';
+$lang->programplan->milestone = 'Milestone';
+$lang->programplan->taskProgress = 'Task Progress';
+$lang->programplan->task = 'Task';
+$lang->programplan->begin = 'Begin';
+$lang->programplan->end = 'End';
+$lang->programplan->realBegan = 'Actual Started';
+$lang->programplan->realEnd = 'Actual End';
+$lang->programplan->ac = 'Actual cost';
+$lang->programplan->sv = 'Schedule Variance';
+$lang->programplan->cv = 'Cost Variance';
+$lang->programplan->planDateRange = 'Planned Start';
+$lang->programplan->realDateRange = 'Actual Start';
+$lang->programplan->output = 'Output';
+$lang->programplan->openedBy = 'Created By';
+$lang->programplan->openedDate = 'Created Date';
+$lang->programplan->editedBy = 'Edited By';
+$lang->programplan->editedDate = 'Edited Date';
+$lang->programplan->duration = 'Duration';
+$lang->programplan->version = 'Version';
+$lang->programplan->full = 'Full Screen';
+$lang->programplan->today = 'Today';
+$lang->programplan->exporting = 'Exporting';
+$lang->programplan->exportFail = 'Export failed';
+$lang->programplan->hideCriticalPath = 'Hide Critical Path';
+$lang->programplan->showCriticalPath = 'Show Critical Path';
+
+$lang->programplan->milestoneList[1] = 'Yes';
+$lang->programplan->milestoneList[0] = 'No';
+
+$lang->programplan->noData = 'No Data';
+$lang->programplan->children = 'Sub Plan';
+$lang->programplan->childrenAB = 'Child';
+$lang->programplan->confirmDelete = 'Do you want to delete the current plan?';
+$lang->programplan->workloadTips = 'The proportion of the sub stage workload is divided by 100%.';
+
+$lang->programplan->stageCustom = new stdClass();
+$lang->programplan->stageCustom->date = 'Show Date';
+$lang->programplan->stageCustom->task = 'Show Task';
+
+$lang->programplan->error = new stdclass();
+$lang->programplan->error->percentNumber = '"Workload %" must be digits.';
+$lang->programplan->error->planFinishSmall = 'The "End" date must be > the "Begin" date.';
+$lang->programplan->error->percentOver = 'The sum of "Workload %" cannot exceed 100%.';
+$lang->programplan->error->createdTask = 'The task has been decomposed. Sub phases cannot be added.';
+$lang->programplan->error->parentWorkload = 'The sum of the workload of the child phase cannot be greater than that of the parent phase: %s.';
diff --git a/module/programplan/lang/zh-cn.php b/module/programplan/lang/zh-cn.php
new file mode 100644
index 0000000000..dc91358651
--- /dev/null
+++ b/module/programplan/lang/zh-cn.php
@@ -0,0 +1,73 @@
+
+ * @package programplan
+ * @version $Id: zh-cn.php 4729 2013-05-03 07:53:55Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+$lang->programplan->common = '项目计划';
+$lang->programplan->browse = '浏览阶段计划';
+$lang->programplan->gantt = '甘特图';
+$lang->programplan->list = '阶段列表';
+$lang->programplan->create = '设置阶段';
+$lang->programplan->edit = '编辑';
+$lang->programplan->delete = '删除阶段';
+$lang->programplan->createSubPlan = '创建二级阶段';
+
+$lang->programplan->parent = '父阶段';
+$lang->programplan->emptyParent = '无';
+$lang->programplan->name = '阶段名称';
+$lang->programplan->subStageName = '子阶段名称';
+$lang->programplan->percent = '工作量占比';
+$lang->programplan->percentAB = '工作量占比';
+$lang->programplan->planPercent = '工作量';
+$lang->programplan->attribute = '阶段类型';
+$lang->programplan->milestone = '里程碑';
+$lang->programplan->taskProgress = '任务进度';
+$lang->programplan->task = '任务';
+$lang->programplan->begin = '计划开始';
+$lang->programplan->end = '计划完成';
+$lang->programplan->realBegan = '实际开始';
+$lang->programplan->realEnd = '实际完成';
+$lang->programplan->ac = '实际花费';
+$lang->programplan->sv = '进度偏差率';
+$lang->programplan->cv = '成本偏差率';
+$lang->programplan->planDateRange = '计划起始日期';
+$lang->programplan->realDateRange = '实际起始日期';
+$lang->programplan->output = '输出';
+$lang->programplan->openedBy = '由谁创建';
+$lang->programplan->openedDate = '创建日期';
+$lang->programplan->editedBy = '由谁编辑';
+$lang->programplan->editedDate = '编辑日期';
+$lang->programplan->duration = '工期';
+$lang->programplan->version = '版本号';
+$lang->programplan->full = '全屏';
+$lang->programplan->today = '今天';
+$lang->programplan->exporting = '导出';
+$lang->programplan->exportFail = '导出失败';
+$lang->programplan->hideCriticalPath = '隐藏关键路径';
+$lang->programplan->showCriticalPath = '显示关键路径';
+
+$lang->programplan->milestoneList[1] = '是';
+$lang->programplan->milestoneList[0] = '否';
+
+$lang->programplan->noData = '暂无数据。';
+$lang->programplan->children = '二级计划';
+$lang->programplan->childrenAB = '子';
+$lang->programplan->confirmDelete = '确定要删除当前计划吗?';
+$lang->programplan->workloadTips = '子阶段工作量占比按百分百的比例进行拆分';
+
+$lang->programplan->stageCustom = new stdClass();
+$lang->programplan->stageCustom->date = '显示日期';
+$lang->programplan->stageCustom->task = '显示任务';
+
+$lang->programplan->error = new stdclass();
+$lang->programplan->error->percentNumber = '"工作量比例"必须为数字';
+$lang->programplan->error->planFinishSmall = '"计划完成时间"必须大于"计划开始时间"';
+$lang->programplan->error->percentOver = '工作量占比累计不应当超过100%';
+$lang->programplan->error->createdTask = '已分解任务,不可添加子阶段';
+$lang->programplan->error->parentWorkload = '子阶段的工作量之和不能大于父阶段的工作量:%s';
diff --git a/module/programplan/lang/zh-tw.php b/module/programplan/lang/zh-tw.php
new file mode 100644
index 0000000000..7afefe8a6f
--- /dev/null
+++ b/module/programplan/lang/zh-tw.php
@@ -0,0 +1,73 @@
+
+ * @package programplan
+ * @version $Id: zh-tw.php 4729 2013-05-03 07:53:55Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+$lang->programplan->common = '項目計劃';
+$lang->programplan->browse = '瀏覽階段計劃';
+$lang->programplan->gantt = '甘特圖';
+$lang->programplan->list = '階段列表';
+$lang->programplan->create = '設置階段';
+$lang->programplan->edit = '編輯';
+$lang->programplan->delete = '刪除';
+$lang->programplan->createSubPlan = '創建二級階段';
+
+$lang->programplan->parent = '父階段';
+$lang->programplan->emptyParent = '無';
+$lang->programplan->name = '階段名稱';
+$lang->programplan->subStageName = '子階段名稱';
+$lang->programplan->percent = '工作量占比';
+$lang->programplan->percentAB = '工作量占比';
+$lang->programplan->planPercent = '工作量';
+$lang->programplan->attribute = '階段類型';
+$lang->programplan->milestone = '里程碑';
+$lang->programplan->taskProgress = '任務進度';
+$lang->programplan->task = '任務';
+$lang->programplan->begin = '計劃開始';
+$lang->programplan->end = '計劃完成';
+$lang->programplan->realBegan = '實際開始';
+$lang->programplan->realEnd = '實際完成';
+$lang->programplan->ac = '實際花費';
+$lang->programplan->sv = '進度偏差率';
+$lang->programplan->cv = '成本偏差率';
+$lang->programplan->planDateRange = '計划起始日期';
+$lang->programplan->realDateRange = '實際起始日期';
+$lang->programplan->output = '輸出';
+$lang->programplan->openedBy = '由誰創建';
+$lang->programplan->openedDate = '創建日期';
+$lang->programplan->editedBy = '由誰編輯';
+$lang->programplan->editedDate = '編輯日期';
+$lang->programplan->duration = '計劃工期';
+$lang->programplan->version = '版本號';
+$lang->programplan->full = '全屏';
+$lang->programplan->today = '今天';
+$lang->programplan->exporting = '導出';
+$lang->programplan->exportFail = '導出失敗';
+$lang->programplan->hideCriticalPath = '隱藏關鍵路徑';
+$lang->programplan->showCriticalPath = '顯示關鍵路徑';
+
+$lang->programplan->milestoneList[1] = '是';
+$lang->programplan->milestoneList[0] = '否';
+
+$lang->programplan->noData = '暫無數據。';
+$lang->programplan->children = '二級計劃';
+$lang->programplan->childrenAB = '子';
+$lang->programplan->confirmDelete = '確定要刪除當前計劃嗎?';
+$lang->programplan->workloadTips = '子階段工作量占比按百分百的比例進行拆分';
+
+$lang->programplan->stageCustom = new stdClass();
+$lang->programplan->stageCustom->date = '顯示日期';
+$lang->programplan->stageCustom->task = '顯示任務';
+
+$lang->programplan->error = new stdclass();
+$lang->programplan->error->percentNumber = '"工作量比例"必須為數字';
+$lang->programplan->error->planFinishSmall = '"計劃完成時間"必須大於"計劃開始時間"';
+$lang->programplan->error->percentOver = '工作量占比累計不應當超過100%';
+$lang->programplan->error->createdTask = '已分解任務,不可添加子階段';
+$lang->programplan->error->parentWorkload = '子階段的工作量之和不能大於父階段的工作量:%s';
diff --git a/module/programplan/model.php b/module/programplan/model.php
new file mode 100644
index 0000000000..b0f8d556da
--- /dev/null
+++ b/module/programplan/model.php
@@ -0,0 +1,918 @@
+
+ * @package programplan
+ * @version $Id: model.php 5079 2013-07-10 00:44:34Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+?>
+dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($planID)->fetch();
+
+ return $this->processPlan($plan);
+ }
+
+ /**
+ * Get plans list.
+ *
+ * @param int $executionID
+ * @param int $productID
+ * @param string $browseType all|parent
+ * @param string $orderBy
+ * @access public
+ * @return array
+ */
+ public function getStage($executionID = 0, $productID = 0, $browseType = 'all', $orderBy = 'id_asc')
+ {
+ if(empty($executionID) || empty($productID)) return array();
+
+ $plans = $this->dao->select('t2.*')->from(TABLE_PROJECTPRODUCT)->alias('t1')
+ ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
+ ->where('t1.product')->eq($productID)
+ ->andWhere('t2.type')->eq('stage')
+ ->beginIF($browseType == 'all')->andWhere('t2.project')->eq($executionID)->fi()
+ ->beginIF($browseType == 'parent')->andWhere('t2.parent')->eq($executionID)->fi()
+ ->beginIF(!$this->app->user->admin)->andWhere('t2.id')->in($this->app->user->view->sprints)->fi()
+ ->andWhere('t2.deleted')->eq('0')
+ ->orderBy($orderBy)
+ ->fetchAll('id');
+
+ return $this->processPlans($plans);
+ }
+
+ /**
+ * Get plans by idList.
+ *
+ * @param array $idList
+ * @access public
+ * @return array
+ */
+ public function getByList($idList = array())
+ {
+ $plans = $this->dao->select('*')->from(TABLE_PROJECT)
+ ->where('id')->in($idList)
+ ->andWhere('type')->eq('project')
+ ->fetchAll('id');
+
+ return $this->processPlans($plans);
+ }
+
+ /**
+ * Get plans.
+ *
+ * @param int $executionID
+ * @param int $productID
+ * @param string $orderBy
+ * @access public
+ * @return array
+ */
+ public function getPlans($executionID = 0, $productID = 0, $orderBy = 'id_asc')
+ {
+ $plans = $this->getStage($executionID, $productID, 'all', $orderBy);
+
+ $parents = array();
+ $children = array();
+ foreach($plans as $planID => $plan)
+ {
+ $plan->grade == 1 ? $parents[$planID] = $plan : $children[$plan->parent][] = $plan;
+ }
+
+ foreach($parents as $planID => $plan) $parents[$planID]->children = isset($children[$planID]) ? $children[$planID] : array();
+
+ return $parents;
+ }
+
+ /**
+ * Get pairs.
+ *
+ * @param int $executionID
+ * @param int $productID
+ * @param string $type
+ * @access public
+ * @return array
+ */
+ public function getPairs($executionID, $productID = 0, $type = 'all')
+ {
+ $plans = $this->getPlans($executionID, $productID);
+
+ $pairs = array(0 => '');
+ foreach($plans as $plan)
+ {
+ $pairs[$plan->id] = $plan->name;
+ if(!empty($plan->children))
+ {
+ foreach($plan->children as $child) $pairs[$child->id] = $plan->name . '/' . $child->name;
+ }
+ }
+
+ return $pairs;
+ }
+
+ /**
+ * Get gantt data.
+ *
+ * @param int $executionID
+ * @param int $productID
+ * @param int $baselineID
+ * @param string $selectCustom
+ * @param bool $returnJson
+ * @access public
+ * @return string
+ */
+ public function getDataForGantt($executionID, $productID, $baselineID = 0, $selectCustom = '', $returnJson = true)
+ {
+ $this->loadModel('stage');
+
+ $plans = $this->getStage($executionID, $productID);
+ if($baselineID)
+ {
+ $baseline = $this->loadModel('cm')->getByID($baselineID);
+ $oldData = json_decode($baseline->data);
+ $oldPlans = $oldData->stage;
+ foreach($oldPlans as $id => $oldPlan)
+ {
+ if(!isset($plans[$id])) continue;
+ $plans[$id]->version = $oldPlan->version;
+ $plans[$id]->name = $oldPlan->name;
+ $plans[$id]->milestone = $oldPlan->milestone;
+ $plans[$id]->begin = $oldPlan->begin;
+ $plans[$id]->end = $oldPlan->end;
+ }
+ }
+
+ $datas = array();
+ $planIDList = array();
+ $isMilestone = "
";
+ $stageIndex = array();
+ foreach($plans as $plan)
+ {
+ $planIDList[$plan->id] = $plan->id;
+
+ $start = helper::isZeroDate($plan->begin) ? '' : $plan->begin;
+ $end = helper::isZeroDate($plan->end) ? '' : $plan->end;
+
+ $data = new stdclass();
+ $data->id = $plan->id;
+ $data->type = 'plan';
+ $data->text = empty($plan->milestone) ? $plan->name : $plan->name . $isMilestone ;
+ $data->percent = $plan->percent;
+ $data->attribute = zget($this->lang->stage->typeList, $plan->attribute);
+ $data->milestone = zget($this->lang->programplan->milestoneList, $plan->milestone);
+ $data->begin = $start;
+ $data->deadline = $end;
+ $data->realBegan = helper::isZeroDate($plan->realBegan) ? '' : substr($plan->realBegan, 0, 10);
+ $data->realEnd = helper::isZeroDate($plan->realEnd) ? '' : substr($plan->realEnd, 0, 10);
+ $data->parent = $plan->grade == 1 ? 0 :$plan->parent;
+ $data->open = true;
+ $data->start_date = $data->realBegan ? $data->realBegan : $data->begin;
+ $data->endDate = $data->realEnd ? $data->realEnd : $data->deadline;
+ $data->duration = 0;
+
+ if($data->endDate > $data->start_date) $data->duration = helper::diffDate($data->endDate, $data->start_date) + 1;
+ if($data->start_date) $data->start_date = date('d-m-Y', strtotime($data->start_date));
+ if($data->start_date == '' or $data->endDate == '') $data->duration = 0;
+
+ $datas['data'][] = $data;
+ $stageIndex[] = array('planID' => $plan->id, 'progress' => array('totalConsumed' => 0, 'totalReal' => 0));
+ }
+
+ $taskSign = "
[ T ] ";
+ $taskPri = "
%s ";
+
+ /* Judge whether to display tasks under the stage. */
+ $owner = $this->app->user->account;
+ $module = 'programplan';
+ $section = 'browse';
+ $object = 'stageCustom';
+
+ if(empty($selectCustom)) $selectCustom = $this->loadModel('setting')->getItem("owner={$owner}&module={$module}§ion={$section}&key={$object}");
+
+ $tasks = array();
+ if(strpos($selectCustom, 'task') !== false)
+ {
+ $tasks = $this->dao->select('*')->from(TABLE_TASK)->where('deleted')->eq(0)->andWhere('execution')->in($planIDList)->fetchAll('id');
+ }
+
+ if($baselineID)
+ {
+ $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;
+ }
+ }
+
+ foreach($tasks as $task)
+ {
+ $start = helper::isZeroDate($task->estStarted) ? '' : $task->estStarted;
+ $end = helper::isZeroDate($task->deadline) ? '' : $task->deadline;
+
+ $realBegan = helper::isZeroDate($task->realStarted) ? '' : substr($task->realStarted, 0, 10);
+ $realEnd = helper::isZeroDate($task->finishedDate) ? '' : substr($task->finishedDate, 0, 10);
+ $priIcon = sprintf($taskPri, $task->pri, $task->pri, $task->pri);
+
+ $data = new stdclass();
+ $data->id = $task->execution . '-' . $task->id;
+ $data->type = 'task';
+ $data->text = $taskSign . $priIcon . $task->name;
+ $data->percent = '';
+ $data->attribute = '';
+ $data->milestone = '';
+ $data->begin = $start;
+ $data->deadline = $end;
+ $data->realBegan = $realBegan;
+ $data->realEnd = $realEnd;
+ $data->parent = $task->parent > 0 ? $task->execution . '-' . $task->parent : $task->execution;
+ $data->open = true;
+ $progress = $task->consumed ? round($task->consumed / ($task->left + $task->consumed), 3) * 100 : 0;
+ $data->taskProgress = $progress . '%';
+ $data->start_date = $data->realBegan ? $data->realBegan : $data->begin;
+ $data->endDate = $data->realEnd ? $data->realEnd : $data->deadline;
+ $data->duration = 0;
+
+ if($data->endDate > $data->start_date) $data->duration = helper::diffDate($data->endDate, $data->start_date) + 1;
+ if($data->start_date) $data->start_date = date('d-m-Y', strtotime($data->start_date));
+ if($data->start_date == '' or $data->endDate == '') $data->duration = 0;
+
+ $datas['data'][] = $data;
+ foreach($stageIndex as $index => $stage)
+ {
+ if($stage['planID'] == $task->execution)
+ {
+ $stageIndex[$index]['progress']['totalConsumed'] += $task->consumed;
+ $stageIndex[$index]['progress']['totalReal'] += ($task->left + $task->consumed);
+ }
+ }
+ }
+
+ /* Calculate the progress of the phase. */
+ foreach($stageIndex as $index => $stage)
+ {
+ $progress = empty($stage['progress']['totalConsumed']) ? 0 : round($stage['progress']['totalConsumed'] / $stage['progress']['totalReal'], 3) * 100;
+ $progress .= '%';
+ $datas['data'][$index]->taskProgress = $progress;
+ }
+
+ return $returnJson ? json_encode($datas) : $datas;
+ }
+
+ /**
+ * Get total percent.
+ *
+ * @param object $stage
+ * @param object $parent
+ * @access public
+ * @return int
+ */
+ public function getTotalPercent($stage, $parent = false)
+ {
+ /* When parent is equal to true, query the total workload of the subphase. */
+ $executionID = $parent ? $stage->id : $stage->project;
+ $plans = $this->getStage($executionID, $stage->product, 'parent');
+
+ $totalPercent = 0;
+ $stageID = $stage->id;
+ foreach($plans as $id => $stage)
+ {
+ if($id == $stageID) continue;
+ $totalPercent += $stage->percent;
+ }
+
+ return $totalPercent;
+ }
+
+ /**
+ * Process plans.
+ *
+ * @param int $plans
+ * @access public
+ * @return object
+ */
+ public function processPlans($plans)
+ {
+ foreach($plans as $planID => $plan) $plans[$planID] = $this->processPlan($plan);
+ return $plans;
+ }
+
+ /**
+ * Process plan.
+ *
+ * @param int $plan
+ * @access public
+ * @return object
+ */
+ public function processPlan($plan)
+ {
+ $plan->setMilestone = true;
+
+ if($plan->parent)
+ {
+ $attribute = $this->dao->select('attribute')->from(TABLE_PROJECT)->where('id')->eq($plan->parent)->fetch('attribute');
+ $plan->attribute = $attribute == 'develop' ? $attribute : $plan->attribute;
+ }
+ else
+ {
+ $milestones = $this->dao->select('count(*) AS count')->from(TABLE_PROJECT)
+ ->where('parent')->eq($plan->id)
+ ->andWhere('milestone')->eq(1)
+ ->andWhere('deleted')->eq(0)
+ ->fetch('count');
+ if($milestones > 0)
+ {
+ $plan->milestone = 0;
+ $plan->setMilestone = false;
+ }
+ }
+
+ $plan->begin = $plan->begin == '0000-00-00' ? '' : $plan->begin;
+ $plan->end = $plan->end == '0000-00-00' ? '' : $plan->end;
+ $plan->realBegan = $plan->realBegan == '0000-00-00' ? '' : $plan->realBegan;
+ $plan->realEnd = $plan->realEnd == '0000-00-00' ? '' : $plan->realEnd;
+
+ $plan->product = $this->loadModel('product')->getProductIDByProject($plan->id);
+ $plan->productName = $this->dao->findByID($plan->product)->from(TABLE_PRODUCT)->fetch('name');
+
+ return $plan;
+ }
+
+ /**
+ * Get duration.
+ *
+ * @param int $begin
+ * @param int $end
+ * @access public
+ * @return int
+ */
+ public function getDuration($begin, $end)
+ {
+ $duration = $this->loadModel('holiday')->getActualWorkingDays($begin, $end);
+ return count($duration);
+ }
+
+ /**
+ * Create a plan.
+ *
+ * @param int $projectID
+ * @param int $productID
+ * @param int $parentID
+ * @access public
+ * @return bool
+ */
+ public function create($projectID = 0, $productID = 0, $parentID = 0)
+ {
+ $data = (array)fixer::input('post')->get();
+ extract($data);
+
+ /* Determine if a task has been created under the parent phase. */
+ if(!$this->isCreateTask($parentID)) return dao::$errors['message'][] = $this->lang->programplan->error->createdTask;
+
+ /* The child phase type setting is the same as the parent phase. */
+ $parentAttribute = '';
+ $parentPercent = 0;
+ if($parentID)
+ {
+ $parentStage = $this->getByID($parentID);
+ $parentAttribute = $parentStage->attribute;
+ $parentPercent = $parentStage->percent;
+ $parentACL = $parentStage->acl;
+ }
+
+ $attributes = array_values($attributes);
+ $milestone = array_values($milestone);
+ $datas = array();
+ foreach($names as $key => $name)
+ {
+ if(empty($name)) continue;
+
+ $plan = new stdclass();
+ $plan->id = isset($planIDList[$key]) ? $planIDList[$key] : '';
+ $plan->type = 'stage';
+ $plan->project = $projectID;
+ $plan->parent = $parentID ? $parentID : $projectID;
+ $plan->name = $names[$key];
+ $plan->percent = $percents[$key];
+ $plan->attribute = empty($parentID) ? $attributes[$key] : $parentAttribute;
+ $plan->milestone = $milestone[$key];
+ $plan->begin = empty($begin[$key]) ? '0000-00-00' : $begin[$key];
+ $plan->end = empty($end[$key]) ? '0000-00-00' : $end[$key];
+ $plan->realBegan = empty($realBegan[$key]) ? '0000-00-00' : $realBegan[$key];
+ $plan->realEnd = empty($realEnd[$key]) ? '0000-00-00' : $realEnd[$key];
+ $plan->output = empty($output[$key]) ? '' : implode(',', $output[$key]);
+ $plan->acl = empty($parentID) ? $acl[$key] : $parentACL;
+
+ $datas[] = $plan;
+ }
+
+ $totalPercent = 0;
+ $totalDevType = 0;
+ $milestone = 0;
+ foreach($datas as $plan)
+ {
+ if($plan->percent and !preg_match("/^[0-9]+(.[0-9]{1,3})?$/", $plan->percent))
+ {
+ dao::$errors['message'][] = $this->lang->programplan->error->percentNumber;
+ return false;
+ }
+ if($plan->end != '0000-00-00' and $plan->end < $plan->begin)
+ {
+ dao::$errors['message'][] = $this->lang->programplan->error->planFinishSmall;
+ return false;
+ }
+
+ if($plan->begin == '0000-00-00') $plan->begin = '';
+ if($plan->end == '0000-00-00') $plan->end = '';
+ foreach(explode(',', $this->config->programplan->create->requiredFields) as $field)
+ {
+ $field = trim($field);
+ if($field and empty($plan->$field))
+ {
+ dao::$errors['message'][] = sprintf($this->lang->error->notempty, $this->lang->programplan->$field);
+ return false;
+ }
+ }
+
+ $plan->percent = (float)$plan->percent;
+ $totalPercent += $plan->percent;
+
+ if($plan->milestone) $milestone = 1;
+ }
+
+ if($totalPercent > 100) return dao::$errors['message'][] = $this->lang->programplan->error->percentOver;
+
+ $this->loadModel('action');
+ $this->loadModel('user');
+ $this->loadModel('execution');
+ $this->app->loadLang('doc');
+ $account = $this->app->user->account;
+ $now = helper::now();
+ foreach($datas as $data)
+ {
+ /* Set planDuration and realDuration. */
+ $data->planDuration = $this->getDuration($data->begin, $data->end);
+ $data->realDuration = $this->getDuration($data->realBegan, $data->realEnd);
+
+ $projectChanged = false;
+ $data->days = helper::diffDate($data->end, $data->begin) + 1;
+ if($data->id)
+ {
+ $stageID = $data->id;
+ unset($data->id);
+
+ $oldStage = $this->getByID($stageID);
+ $planChanged = ($oldStage->name != $data->name || $oldStage->milestone != $data->milestone || $oldStage->begin != $data->begin || $oldStage->end != $data->end);
+
+ if($planChanged) $data->version = $oldStage->version + 1;
+ $this->dao->update(TABLE_PROJECT)->data($data)
+ ->autoCheck()
+ ->batchCheck($this->config->programplan->edit->requiredFields, 'notempty')
+ ->checkIF($plan->percent != '', 'percent', 'float')
+ ->where('id')->eq($stageID)
+ ->exec();
+
+ if($data->acl != 'open') $this->user->updateUserView($stageID, 'sprint');
+
+ /* Record version change information. */
+ if($planChanged)
+ {
+ $spec = new stdclass();
+ $spec->project = $stageID;
+ $spec->version = $data->version;
+ $spec->name = $data->name;
+ $spec->milestone = $data->milestone;
+ $spec->begin = $data->begin;
+ $spec->end = $data->end;
+ $this->dao->insert(TABLE_PROJECTSPEC)->data($spec)->exec();
+ }
+
+ $changes = common::createChanges($oldStage, $data);
+ $actionID = $this->action->create('execution', $stageID, 'edited');
+ $this->action->logHistory($actionID, $changes);
+ }
+ else
+ {
+ unset($data->id);
+ $data->status = 'wait';
+ $data->version = 1;
+ $data->parentVersion = $data->parent == 0 ? 0 : $this->dao->findByID($data->parent)->from(TABLE_PROJECT)->fetch('version');
+ $data->team = substr($data->name,0, 30);
+ $data->openedBy = $account;
+ $data->openedDate = $now;
+ $data->openedVersion = $this->config->version;
+ if(!isset($data->acl)) $data->acl = $this->dao->findByID($data->parent)->from(TABLE_PROJECT)->fetch('acl');
+ $this->dao->insert(TABLE_PROJECT)->data($data)
+ ->autoCheck()
+ ->batchCheck($this->config->programplan->create->requiredFields, 'notempty')
+ ->checkIF($plan->percent != '', 'percent', 'float')
+ ->exec();
+
+ if(!dao::isError())
+ {
+ $stageID = $this->dao->lastInsertID();
+
+ if($data->acl != 'open') $this->user->updateUserView($stageID, 'sprint');
+ $this->dao->update(TABLE_PROJECT)->set('`order`')->eq($stageID * 5)->where('id')->eq($stageID)->exec();
+
+ /* Create doc lib. */
+ $lib = new stdclass();
+ $lib->execution = $stageID;
+ $lib->name = str_replace($this->lang->executionCommon, $this->lang->project->stage, $this->lang->doclib->main['execution']);
+ $lib->type = 'execution';
+ $lib->main = '1';
+ $lib->acl = 'default';
+ $this->dao->insert(TABLE_DOCLIB)->data($lib)->exec();
+
+ /* Add creators to stage teams and execution teams. */
+ $member = new stdclass();
+ $member->root = $stageID;
+ $member->account = $account;
+ $member->role = $this->lang->user->roleList[$this->app->user->role];
+ $member->join = $now;
+ $member->type = $data->type;
+ $member->days = $data->days;
+ $member->hours = $this->config->execution->defaultWorkhours;
+ $this->dao->insert(TABLE_TEAM)->data($member)->exec();
+ $this->execution->addProjectMembers($data->project, array($member));
+
+ $this->setTreePath($stageID);
+ if($data->acl != 'open') $this->user->updateUserView($stageID, 'sprint');
+
+ $this->post->set('products', array(0 => $productID));
+ $this->execution->updateProducts($stageID);
+
+ /* Record version change information. */
+ $spec = new stdclass();
+ $spec->project = $stageID;
+ $spec->version = $data->version;
+ $spec->name = $data->name;
+ $spec->milestone = $data->milestone;
+ $spec->begin = $data->begin;
+ $spec->end = $data->end;
+ $this->dao->insert(TABLE_PROJECTSPEC)->data($spec)->exec();
+
+ $this->action->create('execution', $stageID, 'opened', '', join(',', $_POST['products']));
+ }
+ }
+
+ /* If child plans has milestone, update parent plan set milestone eq 0 . */
+ if($parentID and $milestone) $this->dao->update(TABLE_PROJECT)->set('milestone')->eq(0)->where('id')->eq($parentID)->exec();
+
+ if(dao::isError()) die(js::error(dao::getError()));
+ }
+ }
+
+ /**
+ * Set stage tree path.
+ *
+ * @param int $planID
+ * @access public
+ * @return bool
+ */
+ public function setTreePath($planID)
+ {
+ $stage = $this->dao->select('id,type,parent,path,grade')->from(TABLE_PROJECT)->where('id')->eq($planID)->fetch();
+ $parent = $this->dao->select('id,type,parent,path,grade')->from(TABLE_PROJECT)->where('id')->eq($stage->parent)->fetch();
+
+ if($parent->type == 'project')
+ {
+ $path['path'] = ",{$parent->id},{$stage->id},";
+ $path['grade'] = 1;
+ }
+ elseif($parent->type == 'stage')
+ {
+ $path['path'] = $parent->path . "{$stage->id},";
+ $path['grade'] = $parent->grade + 1;
+ }
+ $this->dao->update(TABLE_PROJECT)->set('path')->eq($path['path'])->set('grade')->eq($path['grade'])->where('id')->eq($stage->id)->exec();
+ }
+
+ /**
+ * Update a plan.
+ *
+ * @param int $planID
+ * @param int $projectID
+ * @access public
+ * @return bool|array
+ */
+ public function update($planID = 0, $projectID = 0)
+ {
+ /* Get oldPlan and the data from the post. */
+ $oldPlan = $this->getByID($planID);
+ $plan = fixer::input('post')
+ ->setDefault('begin', '0000-00-00')
+ ->setDefault('end', '0000-00-00')
+ ->setDefault('realBegan', '0000-00-00')
+ ->setDefault('realEnd', '0000-00-00')
+ ->join('output', ',')
+ ->get();
+
+ /* Judgment of required items. */
+ if($plan->begin == '0000-00-00') dao::$errors['begin'][] = sprintf($this->lang->error->notempty, $this->lang->programplan->begin);
+ if($plan->end == '0000-00-00') dao::$errors['end'][] = sprintf($this->lang->error->notempty, $this->lang->programplan->end);
+
+ $planChanged = ($oldPlan->name != $plan->name || $oldPlan->milestone != $plan->milestone || $oldPlan->begin != $plan->begin || $oldPlan->end != $plan->end);
+
+ if($plan->parent > 0)
+ {
+ $parentStage = $this->getByID($plan->parent);
+ $plan->attribute = $parentStage->attribute;
+ $plan->acl = $parentStage->acl;
+ $parentPercent = $parentStage->percent;
+
+ $childrenTotalPercent = $this->getTotalPercent($parentStage, true);
+ $childrenTotalPercent = $plan->parent == $oldPlan->parent ? ($childrenTotalPercent - $oldPlan->percent + $plan->percent) : ($childrenTotalPercent + $plan->percent);
+ if($childrenTotalPercent > 100) return dao::$errors['percent'][] = $this->lang->programplan->error->percentOver;
+
+ /* If child plan has milestone, update parent plan set milestone eq 0 . */
+ if($plan->milestone and $parentStage->milestone) $this->dao->update(TABLE_PROJECT)->set('milestone')->eq(0)->where('id')->eq($oldPlan->parent)->exec();
+ }
+ else
+ {
+ /* Synchronously update sub-phase permissions. */
+ $childrenIDList = $this->dao->select('*')->from(TABLE_PROJECT)->where('parent')->eq($oldPlan->id)->fetch('id');
+ if(!empty($childrenIDList)) $this->dao->update(TABLE_PROJECT)->set('acl')->eq($plan->acl)->where('id')->in($childrenIDList)->exec();
+
+ /* The workload of the parent plan cannot exceed 100%. */
+ $oldPlan->parent = $plan->parent;
+ $totalPercent = $this->getTotalPercent($oldPlan);
+ $totalPercent = $totalPercent + $plan->percent;
+ if($totalPercent > 100) return dao::$errors['percent'][] = $this->lang->programplan->error->percentOver;
+ }
+
+ /* Set planDuration and realDuration. */
+ $plan->planDuration = $this->getDuration($plan->begin, $plan->end);
+ $plan->realDuration = $this->getDuration($plan->realBegan, $plan->realEnd);
+
+ if($planChanged) $plan->version = $oldPlan->version + 1;
+ if(empty($plan->parent)) $plan->parent = $projectID;
+
+ $this->dao->update(TABLE_PROJECT)->data($plan)
+ ->autoCheck()
+ ->batchCheck($this->config->programplan->edit->requiredFields, 'notempty')
+ ->checkIF($plan->end != '0000-00-00', 'end', 'ge', $plan->begin)
+ ->checkIF($plan->percent != false, 'percent', 'float')
+ ->where('id')->eq($planID)
+ ->exec();
+
+ if(dao::isError()) return false;
+ $this->setTreePath($planID);
+ if($plan->acl != 'open') $this->loadModel('user')->updateUserView($planID, 'sprint');
+
+ if($planChanged)
+ {
+ $spec = new stdclass();
+ $spec->project = $planID;
+ $spec->version = $plan->version;
+ $spec->name = $plan->name;
+ $spec->milestone = $plan->milestone;
+ $spec->begin = $plan->begin;
+ $spec->end = $plan->end;
+
+ $this->dao->insert(TABLE_PROJECTSPEC)->data($spec)->exec();
+ }
+
+ return common::createChanges($oldPlan, $plan);
+ }
+
+ /**
+ * Print cell.
+ *
+ * @param int $col
+ * @param int $plan
+ * @param int $users
+ * @param int $projectID
+ * @access public
+ * @return string
+ */
+ public function printCell($col, $plan, $users, $projectID)
+ {
+ $id = $col->id;
+ if($col->show)
+ {
+ $class = 'c-' . $id;
+ $title = '';
+ $idList = array('id','name','output','percent','attribute','version','begin','end','realBegan','realEnd', 'openedBy', 'openedDate');
+ if(in_array($id,$idList))
+ {
+ $class .= ' text-left';
+ $title = "title='{$plan->$id}'";
+ if($id == 'output') $class .= ' text-ellipsis';
+ if(!empty($plan->children)) $class .= ' has-child';
+ }
+ else
+ {
+ $class .= ' text-center';
+ }
+ if($id == 'actions') $class .= ' c-actions';
+
+ echo "
";
+ if(isset($this->config->bizVersion)) $this->loadModel('flow')->printFlowCell('programplan', $plan, $id);
+ switch($id)
+ {
+ case 'id':
+ echo sprintf('%03d', $plan->id);
+ break;
+ case 'name':
+ $milestoneFlag = $plan->milestone ? " lang->programplan->milestone}'>" : '';
+ if($plan->grade > 1) echo '' . $this->lang->programplan->childrenAB . ' ';
+ echo $plan->name . $milestoneFlag;
+ if(!empty($plan->children)) echo '';
+ break;
+ case 'percent':
+ echo $plan->percent . '%';
+ break;
+ case 'attribute':
+ echo zget($this->lang->stage->typeList, $plan->attribute, '');
+ break;
+ case 'begin':
+ echo $plan->begin;
+ break;
+ case 'end':
+ echo $plan->end;
+ break;
+ case 'realBegan':
+ echo $plan->realBegan;
+ break;
+ case 'realEnd':
+ echo $plan->realEnd;
+ break;
+ case 'output':
+ echo $plan->output;
+ break;
+ case 'version':
+ echo $plan->version;
+ break;
+ case 'editedBy':
+ echo zget($users, $plan->editedBy);
+ break;
+ case 'editedDate':
+ echo substr($plan->editedDate, 5, 11);
+ break;
+ case 'openedBy':
+ echo zget($users, $plan->openedBy);
+ break;
+ case 'openedDate':
+ echo substr($plan->openedDate, 5, 11);
+ break;
+ case 'actions':
+ common::printIcon('execution', 'start', "executionID={$plan->id}", $plan, 'list', '', '', 'iframe', true);
+ $class = !empty($plan->children) ? 'disabled' : '';
+ common::printIcon('task', 'create', "executionID={$plan->id}", $plan, 'list', '', '', $class, false, "data-app='execution'");
+
+ if($plan->grade == 1 && $this->isCreateTask($plan->id))
+ {
+ common::printIcon('programplan', 'create', "program={$plan->parent}&productID=$plan->product&planID=$plan->id", $plan, 'list', 'split', '', '', '', '', $this->lang->programplan->createSubPlan);
+ }
+ else
+ {
+ $disabled = ($plan->grade == 2) ? ' disabled' : '';
+ echo html::a('javascript:alert("' . $this->lang->programplan->error->createdTask . '");', '', '', 'class="btn ' . $disabled . '"');
+ }
+
+ common::printIcon('programplan', 'edit', "planID=$plan->id&projectID=$projectID", $plan, 'list', '', '', 'iframe', true);
+
+ $disabled = !empty($plan->children) ? ' disabled' : '';
+ if(common::hasPriv('execution', 'delete', $plan))
+ {
+ common::printIcon('execution', 'delete', "planID=$plan->id&confirm=no", $plan, 'list', 'trash', 'hiddenwin' , $disabled, '', '', $this->lang->programplan->delete);
+ }
+ break;
+ }
+ echo ' | ';
+ }
+ }
+
+ /**
+ * Is create task.
+ *
+ * @param int $planID
+ * @access public
+ * @return bool
+ */
+ public function isCreateTask($planID)
+ {
+ $task = $this->dao->select('*')->from(TABLE_TASK)->where('execution')->eq($planID)->limit(1)->fetch();
+ return empty($task) ? true : false;
+ }
+
+ /**
+ * Is clickable.
+ *
+ * @param int $plan
+ * @param int $action
+ * @static
+ * @access public
+ * @return bool
+ */
+ public static function isClickable($plan, $action)
+ {
+ $action = strtolower($action);
+
+ if($action == 'create' and $plan->grade > 1) return false;
+
+ return true;
+ }
+
+ /**
+ * Get the stage set to milestone.
+ *
+ * @param int $projectID
+ * @access public
+ * @return array
+ */
+ public function getMilestones($projectID = 0)
+ {
+ return $this->dao->select('id, name')->from(TABLE_PROJECT)
+ ->where('project')->eq($projectID)
+ ->andWhere('type')->eq('stage')
+ ->andWhere('milestone')->eq(1)
+ ->andWhere('deleted')->eq(0)
+ ->orderBy('id_desc')
+ ->fetchPairs();
+ }
+
+ /**
+ * Get milestone by product.
+ *
+ * @param int $productID
+ * @access public
+ * @return object
+ */
+ public function getMilestoneByProduct($productID)
+ {
+ return $this->dao->select('t1.id, t1.name')->from(TABLE_PROJECT)->alias('t1')
+ ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.id=t2.project')
+ ->where('t2.product')->eq($productID)
+ ->andWhere('t1.type')->eq('stage')
+ ->andWhere('t1.milestone')->eq(1)
+ ->andWhere('t1.deleted')->eq(0)
+ ->orderBy('t1.begin asc')
+ ->fetchPairs();
+ }
+
+ /**
+ * Get parent stage list.
+ *
+ * @param int $executionID
+ * @param int $planID
+ * @param int $productID
+ * @access public
+ * @return array
+ */
+ public function getParentStageList($executionID, $planID, $productID)
+ {
+ $parentStage = $this->dao->select('t2.id, t2.name')->from(TABLE_PROJECTPRODUCT)
+ ->alias('t1')->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
+ ->where('t1.product')->eq($productID)
+ ->andWhere('t2.project')->eq($executionID)
+ ->andWhere('t2.grade')->eq(1)
+ ->beginIF(!$this->app->user->admin)->andWhere('t2.id')->in($this->app->user->view->sprints)->fi()
+ ->orderBy('t2.id desc')
+ ->fetchPairs();
+
+ /* Remove the currently edited stage. */
+ if(isset($parentStage[$planID])) unset($parentStage[$planID]);
+
+ $plan = $this->getByID($planID);
+ foreach($parentStage as $key => $stage)
+ {
+ $isCreate = $this->isCreateTask($key);
+ if($isCreate === false and $key != $plan->parent) unset($parentStage[$key]);
+ }
+ $parentStage[0] = $this->lang->programplan->emptyParent;
+ ksort($parentStage);
+
+ return $parentStage;
+ }
+}
diff --git a/module/programplan/view/browse.html.php b/module/programplan/view/browse.html.php
new file mode 100644
index 0000000000..c732b2be1d
--- /dev/null
+++ b/module/programplan/view/browse.html.php
@@ -0,0 +1,38 @@
+
+ * @package programplan
+ * @version $Id: browse.html.php 4903 2013-06-26 05:32:59Z wyd621@gmail.com $
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+
+
+
+
+ programplan->noData;?>
+
+ createLink('programplan', 'create', "projectID=$projectID&productID=$productID"), " " . $lang->programplan->create, '', "class='btn btn-info'");?>
+
+
+
+
+
+
+
+
+
+
diff --git a/module/programplan/view/create.html.php b/module/programplan/view/create.html.php
new file mode 100644
index 0000000000..0dc384c14b
--- /dev/null
+++ b/module/programplan/view/create.html.php
@@ -0,0 +1,184 @@
+
+ * @package programplan
+ * @version $Id: create.html.php 4903 2013-06-26 05:32:59Z wyd621@gmail.com $
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+
+
+
+ programplan->create;
+ if($planID) $title = $programPlan->name . $lang->project->stage . '(' . $programPlan->begin . $lang->project->to . $programPlan->end . ')';
+ echo "{$title}";
+ ?>
+
+
+
+
+
+programplan->name : $lang->programplan->subStageName;?>
+
+
+
+
diff --git a/module/programplan/view/edit.html.php b/module/programplan/view/edit.html.php
new file mode 100644
index 0000000000..adb92bb94f
--- /dev/null
+++ b/module/programplan/view/edit.html.php
@@ -0,0 +1,107 @@
+
+ * @package programplan
+ * @version $Id: edit.html.php 4903 2013-06-26 05:32:59Z wyd621@gmail.com $
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+
+
+
programplan->edit;?>
+
+
+
+
+
+
diff --git a/module/programplan/view/gantt.html.php b/module/programplan/view/gantt.html.php
new file mode 100644
index 0000000000..41fda4a4b9
--- /dev/null
+++ b/module/programplan/view/gantt.html.php
@@ -0,0 +1,436 @@
+
+ * @package programplan
+ * @version $Id: gantt.html.php 4903 2013-06-26 05:32:59Z wyd621@gmail.com $
+ * @link http://www.zentao.net
+ */
+?>
+
+
+createLink('programplan', 'ajaxCustom'));?>
+
+rawModule);?>
+rawMethod);?>
+
+
+
diff --git a/module/programplan/view/list.html.php b/module/programplan/view/list.html.php
new file mode 100644
index 0000000000..0468da3c59
--- /dev/null
+++ b/module/programplan/view/list.html.php
@@ -0,0 +1,89 @@
+
+ * @package programplan
+ * @version $Id: list.html.php 4903 2013-06-26 05:32:59Z wyd621@gmail.com $
+ * @link http://www.zentao.net
+ */
+?>
+programplan->confirmDelete);
+?>
+
+
+
diff --git a/module/project/model.php b/module/project/model.php
index 8900dc5bab..2e204e57e4 100644
--- a/module/project/model.php
+++ b/module/project/model.php
@@ -1427,10 +1427,18 @@ class projectModel extends model
{
$canOrder = common::hasPriv('project', 'updateOrder');
$canBatchEdit = common::hasPriv('project', 'batchEdit');
- $projectLink = $this->config->systemMode == 'new' ? helper::createLink('project', 'index', "projectID=$project->id", '', '', $project->id) : helper::createLink('execution', 'task', "projectID=$project->id");
$account = $this->app->user->account;
$id = $col->id;
+ if($project->model == 'waterfall')
+ {
+ $projectLink = helper::createLink('programplan', 'browse', "projectID=$project->id&productID=0&type=lists", '', '', $project->id);
+ }
+ else
+ {
+ $projectLink = $this->config->systemMode == 'new' ? helper::createLink('project', 'index', "projectID=$project->id", '', '', $project->id) : helper::createLink('execution', 'task', "projectID=$project->id");
+ }
+
if($col->show)
{
$title = '';
@@ -1480,11 +1488,8 @@ class projectModel extends model
case 'name':
$prefix = '';
$suffix = '';
- if(isset($this->config->maxVersion))
- {
- if($project->model === 'waterfall') $prefix = "
{$this->lang->project->waterfall} ";
- if($project->model === 'scrum') $prefix = "
{$this->lang->project->scrum} ";
- }
+ if($project->model === 'waterfall') $prefix = "
{$this->lang->project->waterfall} ";
+ if($project->model === 'scrum') $prefix = "
{$this->lang->project->scrum} ";
if(isset($project->delay)) $suffix = "
{$this->lang->project->statusList['delay']}";
if(!empty($suffix) || !empty($prefix)) echo '
';
if(!empty($prefix)) echo $prefix;
@@ -1938,6 +1943,15 @@ class projectModel extends model
global $lang;
$project = $this->getByID($objectID);
+ if(isset($project->model) and $project->model == 'waterfall')
+ {
+ global $lang;
+ $this->loadModel('execution');
+ $lang->executionCommon = $lang->project->stage;
+
+ include $this->app->getModulePath('', 'execution') . 'lang/' . $this->app->getClientLang() . '.php';
+ }
+
$model = 'scrum';
if($project) $model = $project->model;
diff --git a/module/project/view/browsebylist.html.php b/module/project/view/browsebylist.html.php
index fee692f71b..71ba79a392 100644
--- a/module/project/view/browsebylist.html.php
+++ b/module/project/view/browsebylist.html.php
@@ -47,7 +47,7 @@
", '', "class='btn btn-icon' title='{$lang->project->bycard}' id='switchButton' data-type='bycard'");?>
" . $lang->export, '', "class='btn btn-link export'")?>
- config->maxVersion) and !defined('TUTORIAL')):?>
+
' . $lang->project->create, '', 'class="btn btn-primary create-project-btn" data-toggle="modal"');?>
' . $lang->project->create, '', 'class="btn btn-primary create-project-btn"');?>
@@ -71,7 +71,7 @@
project->empty;?>
- config->maxVersion) and !defined('TUTORIAL')):?>
+
' . $lang->project->create, '', 'class="btn btn-info" data-toggle="modal"');?>
config->systemMode == 'new'):?>
' . $lang->project->create, '', 'class="btn btn-info"');?>
diff --git a/module/stage/control.php b/module/stage/control.php
new file mode 100644
index 0000000000..6fa1d394ae
--- /dev/null
+++ b/module/stage/control.php
@@ -0,0 +1,185 @@
+
+ * @package stage
+ * @version $Id: control.php 5107 2013-07-12 01:46:12Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+class stage extends control
+{
+ /**
+ * Browse stages.
+ *
+ * @param string $orderBy
+ * @access public
+ * @return void
+ */
+ public function browse($orderBy = "id_asc")
+ {
+ $this->view->stages = $this->stage->getStages($orderBy);
+ $this->view->orderBy = $orderBy;
+ $this->view->title = $this->lang->stage->common . $this->lang->colon . $this->lang->stage->browse;
+ $this->view->position[] = $this->lang->stage->common;
+ $this->view->position[] = $this->lang->stage->browse;
+
+ $this->display();
+ }
+
+ /**
+ * Create a stage.
+ *
+ * @access public
+ * @return void
+ */
+ public function create()
+ {
+ if($_POST)
+ {
+ $stageID = $this->stage->create();
+
+ $response['result'] = 'success';
+ $response['message'] = $this->lang->saveSuccess;
+ if(!$stageID)
+ {
+ $response['result'] = 'fail';
+ $response['message'] = dao::getError();
+ return $this->send($response);
+ }
+
+ $this->loadModel('action')->create('stage', $stageID, 'Opened');
+ $response['locate'] = inlink('browse');
+ return $this->send($response);
+ }
+
+ $this->view->title = $this->lang->stage->common . $this->lang->colon . $this->lang->stage->create;
+ $this->view->position[] = $this->lang->stage->common;
+ $this->view->position[] = $this->lang->stage->create;
+
+ $this->display();
+ }
+
+ /**
+ * Batch create stages.
+ *
+ * @access public
+ * @return void
+ */
+ public function batchCreate()
+ {
+ if($_POST)
+ {
+ $this->stage->batchCreate();
+
+ $response['result'] = 'success';
+ $response['message'] = $this->lang->saveSuccess;
+ if(dao::isError())
+ {
+ $response['result'] = 'fail';
+ $response['message'] = dao::getError();
+ return $this->send($response);
+ }
+
+ $response['locate'] = inlink('browse');
+ return $this->send($response);
+ }
+
+ $this->view->title = $this->lang->stage->common . $this->lang->colon . $this->lang->stage->batchCreate;
+ $this->view->position[] = $this->lang->stage->common;
+ $this->view->position[] = $this->lang->stage->batchCreate;
+
+ $this->display();
+ }
+
+ /**
+ * Edit a stage.
+ *
+ * @param int $stageID
+ * @access public
+ * @return void
+ */
+ public function edit($stageID = 0)
+ {
+ $stage = $this->stage->getByID($stageID);
+ if($_POST)
+ {
+ $changes = $this->stage->update($stageID);
+
+ $response['result'] = 'success';
+ $response['message'] = $this->lang->saveSuccess;
+ if(dao::isError())
+ {
+ $response['result'] = 'fail';
+ $response['message'] = dao::getError();
+ return $this->send($response);
+ }
+
+ $actionID = $this->loadModel('action')->create('stage', $stageID, 'Edited');
+ if(!empty($changes)) $this->action->logHistory($actionID, $changes);
+ $response['locate'] = inlink('browse');
+ return $this->send($response);
+ }
+
+ $this->view->title = $this->lang->stage->common . $this->lang->colon . $this->lang->stage->edit;
+ $this->view->position[] = $this->lang->stage->common;
+ $this->view->position[] = $this->lang->stage->edit;
+ $this->view->stage = $stage;
+
+ $this->display();
+ }
+
+ /**
+ * Set type.
+ *
+ * @access public
+ * @return void
+ */
+ public function setType()
+ {
+ $this->loadModel('custom');
+ if($_POST)
+ {
+ $data = fixer::input('post')->get();
+ $this->custom->deleteItems("lang=all&module=stage§ion=typeList");
+ foreach($data->keys as $index => $key)
+ {
+ $value = $data->values[$index];
+ if(!$value or !$key) continue;
+ $this->custom->setItem("all.stage.typeList.{$key}", $value);
+ }
+ return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('stage', 'settype')));
+ }
+
+ $this->view->title = $this->lang->stage->common . $this->lang->colon . $this->lang->stage->setType;
+ $this->view->position[] = $this->lang->stage->common;
+ $this->view->position[] = $this->lang->stage->setType;
+ $this->display();
+ }
+
+ /**
+ * Delete a stage.
+ *
+ * @param int $stageID
+ * @param string $confirm
+ * @access public
+ * @return void
+ */
+ public function delete($stageID, $confirm = 'no')
+ {
+ $stage = $this->stage->getById($stageID);
+
+ if($confirm == 'no')
+ {
+ die(js::confirm($this->lang->stage->confirmDelete, inlink('delete', "stageID=$stageID&confirm=yes")));
+ }
+ else
+ {
+ $this->stage->delete(TABLE_STAGE, $stageID);
+
+ die(js::reload('parent'));
+ }
+ }
+}
diff --git a/module/stage/ext/view/create.cmmi.html.hook.php b/module/stage/ext/view/create.cmmi.html.hook.php
new file mode 100644
index 0000000000..6120b9e1a2
--- /dev/null
+++ b/module/stage/ext/view/create.cmmi.html.hook.php
@@ -0,0 +1 @@
+
diff --git a/module/stage/ext/view/edit.cmmi.html.hook.php b/module/stage/ext/view/edit.cmmi.html.hook.php
new file mode 100644
index 0000000000..6120b9e1a2
--- /dev/null
+++ b/module/stage/ext/view/edit.cmmi.html.hook.php
@@ -0,0 +1 @@
+
diff --git a/module/stage/lang/de.php b/module/stage/lang/de.php
new file mode 100644
index 0000000000..f11a49dfd9
--- /dev/null
+++ b/module/stage/lang/de.php
@@ -0,0 +1,38 @@
+
+ * @package stage
+ * @version $Id: en.php 4729 2013-05-03 07:53:55Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+/* Actions. */
+$lang->stage->browse = 'Stage List';
+$lang->stage->create = 'Create Stage';
+$lang->stage->batchCreate = 'Batch Create';
+$lang->stage->edit = 'Edit';
+$lang->stage->delete = 'Delete';
+$lang->stage->view = 'Details';
+
+/* Fields. */
+$lang->stage->common = 'Stage';
+$lang->stage->id = 'ID';
+$lang->stage->name = 'Name';
+$lang->stage->type = 'Type';
+$lang->stage->percent = 'Workload %';
+$lang->stage->setType = 'Set Type';
+
+$lang->stage->typeList['request'] = 'Story';
+$lang->stage->typeList['design'] = 'Design';
+$lang->stage->typeList['dev'] = 'Development';
+$lang->stage->typeList['qa'] = 'Test';
+$lang->stage->typeList['release'] = 'Release';
+$lang->stage->typeList['review'] = 'Review';
+$lang->stage->typeList['other'] = 'Other';
+
+$lang->stage->viewList = 'Stage List';
+$lang->stage->noStage = 'No stage yet';
+$lang->stage->confirmDelete = 'Do you want to delete it?';
diff --git a/module/stage/lang/en.php b/module/stage/lang/en.php
new file mode 100644
index 0000000000..f11a49dfd9
--- /dev/null
+++ b/module/stage/lang/en.php
@@ -0,0 +1,38 @@
+
+ * @package stage
+ * @version $Id: en.php 4729 2013-05-03 07:53:55Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+/* Actions. */
+$lang->stage->browse = 'Stage List';
+$lang->stage->create = 'Create Stage';
+$lang->stage->batchCreate = 'Batch Create';
+$lang->stage->edit = 'Edit';
+$lang->stage->delete = 'Delete';
+$lang->stage->view = 'Details';
+
+/* Fields. */
+$lang->stage->common = 'Stage';
+$lang->stage->id = 'ID';
+$lang->stage->name = 'Name';
+$lang->stage->type = 'Type';
+$lang->stage->percent = 'Workload %';
+$lang->stage->setType = 'Set Type';
+
+$lang->stage->typeList['request'] = 'Story';
+$lang->stage->typeList['design'] = 'Design';
+$lang->stage->typeList['dev'] = 'Development';
+$lang->stage->typeList['qa'] = 'Test';
+$lang->stage->typeList['release'] = 'Release';
+$lang->stage->typeList['review'] = 'Review';
+$lang->stage->typeList['other'] = 'Other';
+
+$lang->stage->viewList = 'Stage List';
+$lang->stage->noStage = 'No stage yet';
+$lang->stage->confirmDelete = 'Do you want to delete it?';
diff --git a/module/stage/lang/fr.php b/module/stage/lang/fr.php
new file mode 100644
index 0000000000..f11a49dfd9
--- /dev/null
+++ b/module/stage/lang/fr.php
@@ -0,0 +1,38 @@
+
+ * @package stage
+ * @version $Id: en.php 4729 2013-05-03 07:53:55Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+/* Actions. */
+$lang->stage->browse = 'Stage List';
+$lang->stage->create = 'Create Stage';
+$lang->stage->batchCreate = 'Batch Create';
+$lang->stage->edit = 'Edit';
+$lang->stage->delete = 'Delete';
+$lang->stage->view = 'Details';
+
+/* Fields. */
+$lang->stage->common = 'Stage';
+$lang->stage->id = 'ID';
+$lang->stage->name = 'Name';
+$lang->stage->type = 'Type';
+$lang->stage->percent = 'Workload %';
+$lang->stage->setType = 'Set Type';
+
+$lang->stage->typeList['request'] = 'Story';
+$lang->stage->typeList['design'] = 'Design';
+$lang->stage->typeList['dev'] = 'Development';
+$lang->stage->typeList['qa'] = 'Test';
+$lang->stage->typeList['release'] = 'Release';
+$lang->stage->typeList['review'] = 'Review';
+$lang->stage->typeList['other'] = 'Other';
+
+$lang->stage->viewList = 'Stage List';
+$lang->stage->noStage = 'No stage yet';
+$lang->stage->confirmDelete = 'Do you want to delete it?';
diff --git a/module/stage/lang/vi.php b/module/stage/lang/vi.php
new file mode 100644
index 0000000000..f11a49dfd9
--- /dev/null
+++ b/module/stage/lang/vi.php
@@ -0,0 +1,38 @@
+
+ * @package stage
+ * @version $Id: en.php 4729 2013-05-03 07:53:55Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+/* Actions. */
+$lang->stage->browse = 'Stage List';
+$lang->stage->create = 'Create Stage';
+$lang->stage->batchCreate = 'Batch Create';
+$lang->stage->edit = 'Edit';
+$lang->stage->delete = 'Delete';
+$lang->stage->view = 'Details';
+
+/* Fields. */
+$lang->stage->common = 'Stage';
+$lang->stage->id = 'ID';
+$lang->stage->name = 'Name';
+$lang->stage->type = 'Type';
+$lang->stage->percent = 'Workload %';
+$lang->stage->setType = 'Set Type';
+
+$lang->stage->typeList['request'] = 'Story';
+$lang->stage->typeList['design'] = 'Design';
+$lang->stage->typeList['dev'] = 'Development';
+$lang->stage->typeList['qa'] = 'Test';
+$lang->stage->typeList['release'] = 'Release';
+$lang->stage->typeList['review'] = 'Review';
+$lang->stage->typeList['other'] = 'Other';
+
+$lang->stage->viewList = 'Stage List';
+$lang->stage->noStage = 'No stage yet';
+$lang->stage->confirmDelete = 'Do you want to delete it?';
diff --git a/module/stage/lang/zh-cn.php b/module/stage/lang/zh-cn.php
new file mode 100644
index 0000000000..15980382a7
--- /dev/null
+++ b/module/stage/lang/zh-cn.php
@@ -0,0 +1,38 @@
+
+ * @package stage
+ * @version $Id: zh-cn.php 4729 2013-05-03 07:53:55Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+/* Actions. */
+$lang->stage->browse = '阶段列表';
+$lang->stage->create = '新建';
+$lang->stage->batchCreate = '批量新建';
+$lang->stage->edit = '编辑';
+$lang->stage->delete = '删除';
+$lang->stage->view = '阶段详情';
+
+/* Fields. */
+$lang->stage->common = '阶段';
+$lang->stage->id = '编号';
+$lang->stage->name = '阶段名称';
+$lang->stage->type = '阶段分类';
+$lang->stage->percent = '工作量占比';
+$lang->stage->setType = '阶段类型';
+
+$lang->stage->typeList['request'] = '需求';
+$lang->stage->typeList['design'] = '设计';
+$lang->stage->typeList['dev'] = '开发';
+$lang->stage->typeList['qa'] = '测试';
+$lang->stage->typeList['release'] = '发布';
+$lang->stage->typeList['review'] = '总结评审';
+$lang->stage->typeList['other'] = '其他';
+
+$lang->stage->viewList = '浏览列表';
+$lang->stage->noStage = '暂时没有阶段';
+$lang->stage->confirmDelete = '您确定要执行删除操作吗?';
diff --git a/module/stage/lang/zh-tw.php b/module/stage/lang/zh-tw.php
new file mode 100644
index 0000000000..e4e73d5f49
--- /dev/null
+++ b/module/stage/lang/zh-tw.php
@@ -0,0 +1,38 @@
+
+ * @package stage
+ * @version $Id: zh-tw.php 4729 2013-05-03 07:53:55Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+/* Actions. */
+$lang->stage->browse = '階段列表';
+$lang->stage->create = '新建';
+$lang->stage->batchCreate = '批量新建';
+$lang->stage->edit = '編輯';
+$lang->stage->delete = '刪除';
+$lang->stage->view = '階段詳情';
+
+/* Fields. */
+$lang->stage->common = '階段';
+$lang->stage->id = '編號';
+$lang->stage->name = '階段名稱';
+$lang->stage->type = '階段分類';
+$lang->stage->percent = '工作量占比';
+$lang->stage->setType = '階段類型';
+
+$lang->stage->typeList['request'] = '需求';
+$lang->stage->typeList['design'] = '設計';
+$lang->stage->typeList['dev'] = '開發';
+$lang->stage->typeList['qa'] = '測試';
+$lang->stage->typeList['release'] = '發佈';
+$lang->stage->typeList['review'] = '總結評審';
+$lang->stage->typeList['other'] = '其他';
+
+$lang->stage->viewList = '瀏覽列表';
+$lang->stage->noStage = '暫時沒有階段';
+$lang->stage->confirmDelete = '您確定要執行刪除操作嗎?';
diff --git a/module/stage/model.php b/module/stage/model.php
new file mode 100644
index 0000000000..9d4bd1e366
--- /dev/null
+++ b/module/stage/model.php
@@ -0,0 +1,130 @@
+
+ * @package stage
+ * @version $Id: model.php 5079 2013-07-10 00:44:34Z chencongzhi520@gmail.com $
+ * @link http://www.zentao.net
+ */
+?>
+add('createdBy', $this->app->user->account)
+ ->add('createdDate', helper::today())
+ ->get();
+
+ $this->dao->insert(TABLE_STAGE)->data($stage)->autoCheck()->exec();
+
+ if(!dao::isError()) return $this->dao->lastInsertID();
+ return false;
+ }
+
+ /**
+ * Batch create stages.
+ *
+ * @access public
+ * @return bool
+ */
+ public function batchCreate()
+ {
+ $data = fixer::input('post')->get();
+
+ $this->loadModel('action');
+ foreach($data->name as $i => $name)
+ {
+ if(!$name) continue;
+
+ $stage = new stdclass();
+ $stage->name = $name;
+ $stage->percent = $data->percent[$i];
+ $stage->type = $data->type[$i];
+ $stage->createdBy = $this->app->user->account;
+ $stage->createdDate = helper::today();
+
+ $this->dao->insert(TABLE_STAGE)->data($stage)->autoCheck()->exec();
+
+ $stageID = $this->dao->lastInsertID();
+ $this->action->create('stage', $stageID, 'Opened');
+ }
+
+ return true;
+ }
+
+ /**
+ * Update a stage.
+ *
+ * @param int $stageID
+ * @access public
+ * @return bool
+ */
+ public function update($stageID)
+ {
+ $oldStage = $this->dao->select('*')->from(TABLE_STAGE)->where('id')->eq((int)$stageID)->fetch();
+
+ $stage = fixer::input('post')
+ ->add('editedBy', $this->app->user->account)
+ ->add('editedDate', helper::today())
+ ->get();
+
+ $this->dao->update(TABLE_STAGE)->data($stage)->autoCheck()->where('id')->eq((int)$stageID)->exec();
+
+ if(!dao::isError()) return common::createChanges($oldStage, $stage);
+ return false;
+ }
+
+ /**
+ * Get stages.
+ *
+ * @param string $orderBy
+ * @access public
+ * @return array
+ */
+ public function getStages($orderBy = 'id_desc')
+ {
+ return $this->dao->select('*')->from(TABLE_STAGE)->where('deleted')->eq(0)->orderBy($orderBy)->fetchAll('id');
+ }
+
+ /**
+ * Get pairs of stage.
+ *
+ * @access public
+ * @return array
+ */
+ public function getPairs()
+ {
+ $stages = $this->getStages();
+
+ $pairs = array();
+ foreach($stages as $stageID => $stage)
+ {
+ $pairs[$stageID] = $stage->name;
+ }
+
+ return $pairs;
+ }
+
+ /**
+ * Get a stage by id.
+ *
+ * @param int $stageID
+ * @access public
+ * @return object
+ */
+ public function getByID($stageID)
+ {
+ return $this->dao->select('*')->from(TABLE_STAGE)->where('deleted')->eq(0)->andWhere('id')->eq((int)$stageID)->fetch();
+ }
+}
diff --git a/module/stage/view/batchcreate.html.php b/module/stage/view/batchcreate.html.php
new file mode 100644
index 0000000000..6be8c8f996
--- /dev/null
+++ b/module/stage/view/batchcreate.html.php
@@ -0,0 +1,46 @@
+
+ * @package stage
+ * @version $Id: batchCreate.html.php 4903 2013-06-26 05:32:59Z wyd621@gmail.com $
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+
stage->batchCreate;?>
+
+
+
+
diff --git a/module/stage/view/browse.html.php b/module/stage/view/browse.html.php
new file mode 100644
index 0000000000..b60412afff
--- /dev/null
+++ b/module/stage/view/browse.html.php
@@ -0,0 +1,73 @@
+
+ * @package stage
+ * @version $Id: browse.html.php 4903 2013-06-26 05:32:59Z wyd621@gmail.com $
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+ " . $lang->stage->batchCreate, '', "class='btn btn-primary'");?>
+ " . $lang->stage->create, '', "class='btn btn-primary'");?>
+
+
+
+
+
+
+ stage->noStage;?>
+
+ createLink('stage', 'create'), " " . $lang->stage->create, '', "class='btn btn-info'");?>
+
+
+
+
+
+
+
+
+ stage->setType);?>
+ stage->browse, '', "class='selected'");?>
+
+
+
+
+
+
+
+
+
+ | stage->id);?> |
+ stage->name);?> |
+ stage->percent);?> |
+ stage->type);?> |
+ actions;?> |
+
+
+
+
+
+ | id;?> |
+ name;?> |
+ percent;?> |
+ stage->typeList, $stage->type);?> |
+
+ id", "", "list");
+ common::printIcon('stage', 'delete', "stageID=$stage->id", "", "list", '', 'hiddenwin');
+ ?>
+ |
+
+
+
+
+
+
+
+
diff --git a/module/stage/view/create.html.php b/module/stage/view/create.html.php
new file mode 100644
index 0000000000..3d53f89f39
--- /dev/null
+++ b/module/stage/view/create.html.php
@@ -0,0 +1,52 @@
+
+ * @package stage
+ * @version $Id: create.html.php 4903 2013-06-26 05:32:59Z wyd621@gmail.com $
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+
+
stage->create;?>
+
+
+
+
+
diff --git a/module/stage/view/edit.html.php b/module/stage/view/edit.html.php
new file mode 100644
index 0000000000..05c393bdbb
--- /dev/null
+++ b/module/stage/view/edit.html.php
@@ -0,0 +1,51 @@
+
+ * @package stage
+ * @version $Id: edit.html.php 4903 2013-06-26 05:32:59Z wyd621@gmail.com $
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+
diff --git a/module/stage/view/settype.html.php b/module/stage/view/settype.html.php
new file mode 100644
index 0000000000..75d234e5be
--- /dev/null
+++ b/module/stage/view/settype.html.php
@@ -0,0 +1,86 @@
+
+ * @package stage
+ * @version $Id: setType.html.php 4903 2013-06-26 05:32:59Z wyd621@gmail.com $
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+
+ |
+
+
+ |
+
+
+
+ |
+
+EOT;
+?>
+
+
+
+
+
+
+ stage->setType, '', "class='selected'");?>
+ stage->browse);?>
+
+
+
+
+
+
+
+