diff --git a/module/product/view/browse.html.php b/module/product/view/browse.html.php
index d7387a66f0..84f6455101 100644
--- a/module/product/view/browse.html.php
+++ b/module/product/view/browse.html.php
@@ -22,6 +22,7 @@
exportCSV, $lang->setFileName);?>
+ story->report->common); ?>
createLink('story', 'create', "productID=$productID&moduleID=$moduleID"), $lang->story->create); ?>
diff --git a/module/story/control.php b/module/story/control.php
index 1d791b9f43..73b8f4c8a1 100644
--- a/module/story/control.php
+++ b/module/story/control.php
@@ -505,4 +505,44 @@ class story extends control
$this->loadModel('mail')->send($toList, $productName . ':' . 'STORY #' . $story->id . $this->lang->colon . $story->title, $mailContent, $ccList);
if($this->mail->isError()) echo js::error($this->mail->getError());
}
+ /**
+ * The report page.
+ *
+ * @param int $productID
+ * @param string $browseType
+ * @param int $moduleID
+ * @access public
+ * @return void
+ */
+ public function report($productID, $browseType, $moduleID)
+ {
+ $this->loadModel('report');
+ $this->view->charts = array();
+ $this->view->renderJS = '';
+
+ if(!empty($_POST))
+ {
+ foreach($this->post->charts as $chart)
+ {
+ $chartFunc = 'getDataOf' . $chart;
+ $chartData = $this->story->$chartFunc();
+ $chartOption = $this->lang->story->report->$chart;
+ $this->story->mergeChartOption($chart);
+
+ $chartXML = $this->report->createSingleXML($chartData, $chartOption->graph);
+ $this->view->charts[$chart] = $this->report->createJSChart($chartOption->swf, $chartXML, $chartOption->width, $chartOption->height);
+ $this->view->datas[$chart] = $this->report->computePercent($chartData);
+ }
+ $this->view->renderJS = $this->report->renderJsCharts(count($this->view->charts));
+ }
+ $this->products = $this->product->getPairs();
+ $this->product->setMenu($this->products, $productID);
+ $this->view->header->title = $this->products[$productID] . $this->lang->colon . $this->lang->story->common;
+ $this->view->productID = $productID;
+ $this->view->browseType = $browseType;
+ $this->view->moduleID = $moduleID;
+ $this->view->checkedCharts = $this->post->charts ? join(',', $this->post->charts) : '';
+ $this->display();
+ }
+
}
diff --git a/module/story/css/report.css b/module/story/css/report.css
new file mode 100644
index 0000000000..9c3849153e
--- /dev/null
+++ b/module/story/css/report.css
@@ -0,0 +1 @@
+.side span {display:block}
diff --git a/module/story/js/report.js b/module/story/js/report.js
new file mode 100644
index 0000000000..c8c6103f9a
--- /dev/null
+++ b/module/story/js/report.js
@@ -0,0 +1,20 @@
+/* Select All. */
+function checkAll()
+{
+ var checkOBJ = $('input');
+ for(var i = 0; i < checkOBJ.length; i++)
+ {
+ checkOBJ.get(i).checked = true;
+ }
+}
+
+/* Check reverse. */
+function checkReverse()
+{
+ var checkOBJ = $('input');
+ for(var i = 0; i < checkOBJ.length; i++)
+ {
+ checkOBJ.get(i).checked = !checkOBJ.get(i).checked;
+ }
+ return;
+}
diff --git a/module/story/lang/en.php b/module/story/lang/en.php
index 5d006818a4..3b55cd4ce4 100644
--- a/module/story/lang/en.php
+++ b/module/story/lang/en.php
@@ -114,6 +114,7 @@ $lang->story->legendProjectAndTask = 'Project & task';
$lang->story->legendLinkStories = 'Related story';
$lang->story->legendChildStories = 'Child story';
$lang->story->legendSpec = 'Spec';
+$lang->story->legendVerify = 'Verified standard';
$lang->story->legendHistory = 'History';
$lang->story->legendVersion = 'Versions';
$lang->story->legendMisc = 'Misc';
@@ -142,4 +143,56 @@ $lang->story->action->closed = array('main' => '$date, Closed by story->action->linked2plan = array('main' => '$date, Linked to plan $extra by $actor.');
$lang->story->action->unlinkedfromplan = array('main' => '$date, Removed from $extra> by $actor');
$lang->story->action->linked2project = array('main' => '$date, Linked to project $extra by $actor.');
-$lang->story->action->unlinkedfromproject = array('main' => '$date, Removed from project $extra by $actor.');
+$lang->story->action->unlinkedfromproject = array('main' => '$date, Removed from project $extra by $actor.');
+
+/* Report*/
+$lang->story->report->common = 'Report';
+$lang->story->report->select = 'Select';
+$lang->story->report->create = 'Creat';
+$lang->story->report->selectAll = 'All';
+$lang->story->report->selectReverse = 'Reverse';
+
+$lang->story->report->charts['storysPerProduct'] = 'Product storys';
+$lang->story->report->charts['storysPerModule'] = 'Module storys';
+$lang->story->report->charts['storysPerPlan'] = 'Plan storys';
+$lang->story->report->charts['storysPerStatus'] = 'Sotrys of status';
+$lang->story->report->charts['storysPerStage'] = 'Storys of stage';
+$lang->story->report->charts['storysPerPri'] = 'Storys of priority';
+$lang->story->report->charts['storysPerEstimate'] = 'Storys of Estimate';
+$lang->story->report->charts['storysPerOpenedBy'] = 'Opened by user';
+$lang->story->report->charts['storysPerAssignedTo'] = 'Assigned to user';
+$lang->story->report->charts['storysPerClosedReason'] = 'Storys for reason';
+$lang->story->report->charts['storysPerChange'] = 'Storys of change';
+
+$lang->story->report->options->swf = 'pie2d';
+$lang->story->report->options->width = 'auto';
+$lang->story->report->options->height = 300;
+$lang->story->report->options->graph->baseFontSize = 12;
+$lang->story->report->options->graph->showNames = 1;
+$lang->story->report->options->graph->formatNumber = 1;
+$lang->story->report->options->graph->decimalPrecision = 0;
+$lang->story->report->options->graph->animation = 0;
+$lang->story->report->options->graph->rotateNames = 0;
+$lang->story->report->options->graph->yAxisName = 'COUNT';
+$lang->story->report->options->graph->pieRadius = 100; // 饼图直径。
+$lang->story->report->options->graph->showColumnShadow = 0; // 是否显示柱状图阴影。
+
+$lang->story->report->storysPerProduct->graph->xAxisName = 'Product';
+$lang->story->report->storysPerModule->graph->xAxisName = 'Module';
+$lang->story->report->storysPerPlan->graph->xAxisName = 'Plan';
+$lang->story->report->storysPerStatus->graph->xAxisName = 'Status';
+$lang->story->report->storysPerStage->graph->xAxisName = 'Stage';
+$lang->story->report->storysPerPri->graph->xAxisName = 'Priority';
+$lang->story->report->storysPerOpenedBy->graph->xAxisName = 'Opened by';
+$lang->story->report->storysPerAssignedTo->graph->xAxisName = 'Assigned to';
+$lang->story->report->storysPerClosedReason->graph->xAxisName = 'Closed reason';
+
+$lang->story->report->storysPerEstimate->swf = 'column2d';
+$lang->story->report->storysPerEstimate->height = 400;
+$lang->story->report->storysPerEstimate->graph->xAxisName = 'Estimate';
+$lang->story->report->storysPerEstimate->graph->rotateNames = 1;
+
+$lang->story->report->storysPerChange->swf = 'column2d';
+$lang->story->report->storysPerChange->height = 400;
+$lang->story->report->storysPerChange->graph->xAxisName = 'Change';
+$lang->story->report->storysPerChange->graph->rotateNames = 1;
diff --git a/module/story/lang/zh-cn.php b/module/story/lang/zh-cn.php
index 36fa695076..c8ad327595 100644
--- a/module/story/lang/zh-cn.php
+++ b/module/story/lang/zh-cn.php
@@ -144,3 +144,55 @@ $lang->story->action->linked2plan = array('main' => '$date, 由
$lang->story->action->unlinkedfromplan = array('main' => '$date, 由 $actor 从计划 $extra 移除。');
$lang->story->action->linked2project = array('main' => '$date, 由 $actor 关联到项目 $extra。');
$lang->story->action->unlinkedfromproject = array('main' => '$date, 由 $actor 从项目 $extra 移除。');
+
+/* 统计报表。*/
+$lang->story->report->common = '统计报表';
+$lang->story->report->select = '请选择报表类型';
+$lang->story->report->create = '生成报表';
+$lang->story->report->selectAll = '全选';
+$lang->story->report->selectReverse = '反选';
+
+$lang->story->report->charts['storysPerProduct'] = '产品需求数量';
+$lang->story->report->charts['storysPerModule'] = '模块需求数量';
+$lang->story->report->charts['storysPerPlan'] = '计划进行统计';
+$lang->story->report->charts['storysPerStatus'] = '状态进行统计';
+$lang->story->report->charts['storysPerStage'] = '所处阶段进行统计';
+$lang->story->report->charts['storysPerPri'] = '优先级进行统计';
+$lang->story->report->charts['storysPerEstimate'] = '预计工时进行统计';
+$lang->story->report->charts['storysPerOpenedBy'] = '由谁创建来进行统计';
+$lang->story->report->charts['storysPerAssignedTo'] = '当前指派来进行统计';
+$lang->story->report->charts['storysPerClosedReason'] = '关闭原因来进行统计';
+$lang->story->report->charts['storysPerChange'] = '变更次数来进行统计';
+
+$lang->story->report->options->swf = 'pie2d';
+$lang->story->report->options->width = 'auto';
+$lang->story->report->options->height = 300;
+$lang->story->report->options->graph->baseFontSize = 12;
+$lang->story->report->options->graph->showNames = 1;
+$lang->story->report->options->graph->formatNumber = 1;
+$lang->story->report->options->graph->decimalPrecision = 0;
+$lang->story->report->options->graph->animation = 0;
+$lang->story->report->options->graph->rotateNames = 0;
+$lang->story->report->options->graph->yAxisName = 'COUNT';
+$lang->story->report->options->graph->pieRadius = 100; // 饼图直径。
+$lang->story->report->options->graph->showColumnShadow = 0; // 是否显示柱状图阴影。
+
+$lang->story->report->storysPerProduct->graph->xAxisName = '产品';
+$lang->story->report->storysPerModule->graph->xAxisName = '模块';
+$lang->story->report->storysPerPlan->graph->xAxisName = '产品计划';
+$lang->story->report->storysPerStatus->graph->xAxisName = '状态';
+$lang->story->report->storysPerStage->graph->xAxisName = '所处阶段';
+$lang->story->report->storysPerPri->graph->xAxisName = '优先级';
+$lang->story->report->storysPerOpenedBy->graph->xAxisName = '由谁创建';
+$lang->story->report->storysPerAssignedTo->graph->xAxisName = '当前指派';
+$lang->story->report->storysPerClosedReason->graph->xAxisName = '关闭原因';
+
+$lang->story->report->storysPerEstimate->swf = 'column2d';
+$lang->story->report->storysPerEstimate->height = 400;
+$lang->story->report->storysPerEstimate->graph->xAxisName = '预计时间';
+$lang->story->report->storysPerEstimate->graph->rotateNames = 1;
+
+$lang->story->report->storysPerChange->swf = 'column2d';
+$lang->story->report->storysPerChange->height = 400;
+$lang->story->report->storysPerChange->graph->xAxisName = '变更次数';
+$lang->story->report->storysPerChange->graph->rotateNames = 1;
diff --git a/module/story/model.php b/module/story/model.php
index 4d0e46673e..5a9b808895 100644
--- a/module/story/model.php
+++ b/module/story/model.php
@@ -705,4 +705,180 @@ class storyModel extends model
if(!empty($story->lastEditedBy)) $accounts[] = $story->lastEditedBy;
return array_unique($accounts);
}
+ /**
+ * Merge the default chart settings and the settings of current chart.
+ *
+ * @param string $chartType
+ * @access public
+ * @return void
+ */
+ public function mergeChartOption($chartType)
+ {
+ $chartOption = $this->lang->story->report->$chartType;
+ $commonOption = $this->lang->story->report->options;
+
+ $chartOption->graph->caption = $this->lang->story->report->charts[$chartType];
+ if(!isset($chartOption->swf)) $chartOption->swf = $commonOption->swf;
+ if(!isset($chartOption->width)) $chartOption->width = $commonOption->width;
+ if(!isset($chartOption->height)) $chartOption->height = $commonOption->height;
+
+ /* 合并配置。*/
+ foreach($commonOption->graph as $key => $value) if(!isset($chartOption->graph->$key)) $chartOption->graph->$key = $value;
+ }
+
+ /**
+ * Get report data of storys per product
+ *
+ * @access public
+ * @return array
+ */
+ public function getDataOfStorysPerProduct()
+ {
+ $datas = $this->dao->select('product as name, count(product) as value')->from(TABLE_STORY)->where('deleted')->eq(0)->groupBy('product')->orderBy('value DESC')->fetchAll('name');
+ if(!$datas) return array();
+ $products = $this->loadModel('product')->getPairs();
+ foreach($datas as $productID => $data) $data->name = isset($products[$productID]) ? $products[$productID] : $this->lang->report->undefined;
+ return $datas;
+ }
+
+ /**
+ * Get report data of storys per module
+ *
+ * @access public
+ * @return array
+ */
+ public function getDataOfStorysPerModule()
+ {
+ $datas = $this->dao->select('module as name, count(module) as value')->from(TABLE_STORY)->where('deleted')->eq(0)->groupBy('module')->orderBy('value DESC')->fetchAll('name');
+ if(!$datas) return array();
+ $modules = $this->dao->select('id, name')->from(TABLE_MODULE)->where('id')->in(array_keys($datas))->fetchPairs();
+ foreach($datas as $moduleID => $data) $data->name = isset($modules[$moduleID]) ? $modules[$moduleID] : '/';
+ return $datas;
+ }
+ /**
+ * Get report data of storys per plan
+ *
+ * @access public
+ * @return array
+ */
+ public function getDataOfStorysPerPlan()
+ {
+ $datas = $this->dao->select('plan as name, count(plan) as value')->from(TABLE_STORY)
+ ->where('deleted')->eq(0)
+ ->andWhere('plan')->ne(0)
+ ->groupBy('plan')->orderBy('value DESC')->fetchAll('name');
+ if(!$datas) return array();
+ $plans = $this->dao->select('id, title')->from(TABLE_PRODUCTPLAN)->where('id')->in(array_keys($datas))->fetchPairs();
+ foreach($datas as $planID => $data) $data->name = isset($plans[$planID]) ? $plans[$planID] : $this->lang->report->undefined;
+ return $datas;
+ }
+ /**
+ * Get report data of storys per status
+ *
+ * @access public
+ * @return array
+ */
+ public function getDataOfStorysPerStatus()
+ {
+ $datas = $this->dao->select('status as name, count(status) as value')->from(TABLE_STORY)->where('deleted')->eq(0)->groupBy('status')->orderBy('value DESC')->fetchAll('name');
+ if(!$datas) return array();
+ foreach($datas as $status => $data) if(isset($this->lang->story->statusList[$status])) $data->name = $this->lang->story->statusList[$status];
+ return $datas;
+ }
+ /**
+ * Get report data of storys per stage
+ *
+ * @access public
+ * @return array
+ */
+ public function getDataOfStorysPerStage()
+ {
+ $datas = $this->dao->select('stage as name, count(stage) as value')->from(TABLE_STORY)
+ ->where('deleted')->eq(0)
+ ->andWhere('stage')->ne('')
+ ->groupBy('stage')->orderBy('value DESC')->fetchAll('name');
+ if(!$datas) return array();
+ foreach($datas as $stage => $data) if(isset($this->lang->story->stageList[$stage])) $data->name = $this->lang->story->stageList[$stage];
+ return $datas;
+ }
+ /**
+ * Get report data of storys per pri
+ *
+ * @access public
+ * @return array
+ */
+ public function getDataOfStorysPerPri()
+ {
+ $datas = $this->dao->select('pri as name, count(pri) as value')->from(TABLE_STORY)->where('deleted')->eq(0)->groupBy('pri')->orderBy('value DESC')->fetchAll('name');
+ if(!$datas) return array();
+ foreach($datas as $pri => $data) $data->name = $this->lang->story->priList[$pri] != '' ? $this->lang->story->priList[$pri] : $this->lang->report->undefined;
+ return $datas;
+ }
+ /**
+ * Get report data of storys per estimate
+ *
+ * @access public
+ * @return array
+ */
+ public function getDataOfStorysPerEstimate()
+ {
+ return $this->dao->select('estimate as name, count(estimate) as value')->from(TABLE_STORY)->where('deleted')->eq(0)->groupBy('estimate')->orderBy('value')->fetchAll();
+ }
+ /**
+ * Get report data of storys per openedBy
+ *
+ * @access public
+ * @return array
+ */
+ public function getDataOfStorysPerOpenedBy()
+ {
+ $datas = $this->dao->select('openedBy as name, count(openedBy) as value')->from(TABLE_STORY)->where('deleted')->eq(0)->groupBy('openedBy')->orderBy('value DESC')->fetchAll('name');
+ if(!$datas) return array();
+ if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter');
+ foreach($datas as $account => $data) if(isset($this->users[$account])) $data->name = $this->users[$account];
+ return $datas;
+ }
+ /**
+ * Get report data of storys per assignedTo
+ *
+ * @access public
+ * @return array
+ */
+ public function getDataOfStorysPerAssignedTo()
+ {
+ $datas = $this->dao->select('assignedTo as name, count(assignedTo) as value')->from(TABLE_STORY)
+ ->where('deleted')->eq(0)
+ ->andWhere('assignedTo')->ne('')
+ ->groupBy('assignedTo')->orderBy('value DESC')->fetchAll('name');
+ if(!$datas) return array();
+ if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter');
+ foreach($datas as $account => $data) if(isset($this->users[$account])) $data->name = $this->users[$account];
+ return $datas;
+ }
+ /**
+ * Get report data of storys per closedReason
+ *
+ * @access public
+ * @return array
+ */
+ public function getDataOfStorysPerClosedReason()
+ {
+ $datas = $this->dao->select('closedReason as name, count(closedReason) as value')->from(TABLE_STORY)
+ ->where('deleted')->eq(0)
+ ->andWhere('closedReason')->ne('')
+ ->groupBy('closedReason')->orderBy('value DESC')->fetchAll('name');
+ if(!$datas) return array();
+ foreach($datas as $reason => $data) if(isset($this->lang->story->reasonList[$reason])) $data->name = $this->lang->story->reasonList[$reason];
+ return $datas;
+ }
+ /**
+ * Get report data of storys per change
+ *
+ * @access public
+ * @return array
+ */
+ public function getDataOfStorysPerChange()
+ {
+ return $this->dao->select('(version-1) as name, count(*) as value')->from(TABLE_STORY)->where('deleted')->eq(0)->groupBy('version')->orderBy('value')->fetchAll();
+ }
}
diff --git a/module/story/view/report.html.php b/module/story/view/report.html.php
new file mode 100644
index 0000000000..b5265fc9f2
--- /dev/null
+++ b/module/story/view/report.html.php
@@ -0,0 +1,62 @@
+
+ * @package product
+ * @version $Id: report.html.php 1594 2011-03-13 07:27:55Z wwccss $
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
story->report->common;?>
+
goback); ?>
+
+
+
+
+ |
+ story->report->select;?>
+
+
+ |
+ |
+
+
+ story->report->common;?>
+ $chartContent):?>
+
+ |
+
+
+
+ | report->item;?> |
+ report->value;?> |
+ report->percent;?> |
+
+ $data):?>
+
+ | name;?> |
+ value;?> |
+ percent * 100) . '%';?> |
+
+
+
+ |
+
+
+
+ |
+
+
+
+