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->charts, $checkedCharts);?> + + +

+ story->report->create);?> +
+
+ + + $chartContent):?> + + + + + +
story->report->common;?>
+ + + + + + + $data):?> + + + + + + +
report->item;?>report->value;?>report->percent;?>
name;?>value;?>percent * 100) . '%';?>
+
+
+ +