From f7aa8937046d55fea18ef2c55fcba75f4e7adaf5 Mon Sep 17 00:00:00 2001 From: tianshujie Date: Mon, 27 Nov 2023 16:30:17 +0800 Subject: [PATCH] * Refactor program::refreshStats method. --- module/program/model.php | 124 +++------------------------------ module/program/tao.php | 147 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 115 deletions(-) diff --git a/module/program/model.php b/module/program/model.php index 8e7a334146..8a852e61ad 100644 --- a/module/program/model.php +++ b/module/program/model.php @@ -549,7 +549,7 @@ class programModel extends model /* Merge project team. */ $project->teamCount = 0; $project->teamMembers = array(); - $project->leftTasks = isset($data['leftTasks'][$project->id]) ? $data['leftTasks'][$project->id]->tasks : '—'; + $project->leftTasks = isset($leftTasks[$project->id]) ? $leftTasks[$project->id]->tasks : '—'; if(!empty($teams)) { $project->teamCount = isset($teams[$project->id]) ? count($teams[$project->id]) : 0; @@ -1397,13 +1397,14 @@ class programModel extends model } /** + * 刷新项目集的统计数据。 * Refresh stats fields(estimate,consumed,left,progress) of program, project, execution. * * @param bool $refreshAll * @access public * @return void */ - public function refreshStats($refreshAll = false) + public function refreshStats($refreshAll = false): void { $updateTime = zget($this->app->config->global, 'projectStatsTime', ''); $now = helper::now(); @@ -1426,124 +1427,17 @@ class programModel extends model if(empty($projects)) return; } - /* 1. Get summary and members of executions to be refreshed. */ - $tasks = $this->dao->select('t1.id, execution, t1.estimate, t1.consumed, t1.`left`, t1.status')->from(TABLE_TASK)->alias('t1') - ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id') - ->where('t1.deleted')->eq(0) - ->andWhere('t1.parent')->ge(0) // Ignore parent task. - ->beginIF(!empty($projects))->andWhere('t1.project')->in(array_keys($projects))->fi() - ->fetchAll('id'); + /* 1. Refresh stats to db. */ + $this->programTao->updateStats(array_keys($projects)); - $summary = array(); - foreach($tasks as $task) - { - if(empty($task->execution)) continue; - if(!isset($summary[$task->execution])) - { - $summary[$task->execution] = new stdclass(); - $summary[$task->execution]->totalEstimate = 0; - $summary[$task->execution]->totalConsumed = 0; - $summary[$task->execution]->totalLeft = 0; - } + /* 2. Update programStatsTime. */ + $this->programTao->updateProcess(); - $summary[$task->execution]->execution = $task->execution; - $summary[$task->execution]->totalEstimate += $task->estimate; - $summary[$task->execution]->totalConsumed += $task->consumed; - $summary[$task->execution]->totalLeft += ($task->status == 'closed' or $task->status == 'cancel') ? 0 : $task->left; - } - - $teamMembers = $this->dao->select('t1.root, COUNT(1) AS members')->from(TABLE_TEAM)->alias('t1') - ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account=t2.account') - ->where('t1.type')->eq('project') - ->beginIF(!empty($projects))->andWhere('t1.root')->in(array_keys($projects))->fi() - ->andWhere('t2.deleted')->eq(0) - ->groupBy('t1.root') - ->fetchPairs('root'); - - $projectsPairs = $this->dao->select('id,deleted')->from(TABLE_PROJECT)->fetchPairs(); - - /* 2. Get all parents to be refreshed. */ - $executions = array(); - foreach($summary as $execution) $executions[$execution->execution] = $execution->execution; - $paths = $this->dao->select('id,path')->from(TABLE_PROJECT)->where('id')->in($executions)->fetchAll(); - $executionPaths = array(); - foreach($paths as $path) $executionPaths[$path->id] = explode(',', trim($path->path, ',')); - - /* 3. Compute stats of execution and parents. */ - $stats = array(); - foreach($summary as $execution) - { - $executionID = $execution->execution; - foreach($executionPaths[$executionID] as $nodeID) - { - if(!isset($stats[$nodeID])) $stats[$nodeID] = array('totalEstimate' => 0, 'totalConsumed' => 0, 'totalLeft' => 0, 'teamCount' => 0, 'totalLeftNotDel' => 0, 'totalConsumedNotDel' => 0); - $stats[$nodeID]['totalEstimate'] += $execution->totalEstimate; - $stats[$nodeID]['totalConsumed'] += $execution->totalConsumed; - $stats[$nodeID]['totalLeft'] += $execution->totalLeft; - - // Check $execution->execution and $nodeID(path) is not deleted. - if(empty($projectsPairs[$execution->execution]) && empty($projectsPairs[$nodeID])) - { - $stats[$nodeID]['totalConsumedNotDel'] += $execution->totalConsumed; - $stats[$nodeID]['totalLeftNotDel'] += $execution->totalLeft; - } - } - } - - foreach($teamMembers as $projectID => $teamCount) - { - if(!isset($stats[$projectID])) $stats[$projectID] = array('totalEstimate' => 0, 'totalConsumed' => 0, 'totalLeft' => 0, 'teamCount' => 0, 'totalConsumedNotDel' => 0, 'totalLeftNotDel' => 0); - $stats[$projectID]['teamCount'] = $teamCount; - } - - /* 4. Refresh stats to db. */ - foreach($stats as $projectID => $project) - { - $totalRealNotDel = $project['totalConsumedNotDel'] + $project['totalLeftNotDel']; - $progress = $totalRealNotDel ? floor($project['totalConsumedNotDel'] / $totalRealNotDel * 1000) / 1000 * 100 : 0; - $this->dao->update(TABLE_PROJECT) - ->set('progress')->eq($progress) - ->set('teamCount')->eq($project['teamCount']) - ->set('estimate')->eq($project['totalEstimate']) - ->set('consumed')->eq($project['totalConsumedNotDel']) - ->set('left')->eq($project['totalLeftNotDel']) - ->where('id')->eq($projectID) - ->exec(); - } - - /* 5. Update programStatsTime. */ - $projectList = $this->dao->select('id,progress,path,consumed,`left`')->from(TABLE_PROJECT) - ->where('type')->eq('project') - ->andWhere('parent')->ne(0) - ->andWhere('deleted')->eq(0) - ->fetchAll('id'); - $programProgress = array(); - foreach($projectList as $projectID => $project) - { - $path = explode(',', trim($project->path, ',')); - - foreach($path as $programID) - { - if($programID == $projectID) continue; - - if(!isset($programProgress[$programID])) $programProgress[$programID] = array('consumed' => 0, 'left' => 0); - $programProgress[$programID]['consumed'] += $project->consumed; - $programProgress[$programID]['left'] += $project->left; - } - } - - foreach($programProgress as $programID => $hours) - { - $progress = ($hours['consumed'] + $hours['left']) ? floor($hours['consumed'] / ($hours['consumed'] + $hours['left']) * 1000) / 1000 * 100 : 0; - - $this->dao->update(TABLE_PROJECT)->set('progress')->eq($progress)->where('id')->eq($programID)->exec(); - } - - /* 6. Update projectStatsTime in config. */ + /* 3. Update projectStatsTime in config. */ $this->loadModel('setting')->setItem('system.common.global.projectStatsTime', $now); $this->app->config->global->projectStatsTime = $now; - /* 7. Clear actions older than 30 days. */ + /* 4. Clear actions older than 30 days. */ $this->loadModel('action')->cleanActions(); } } diff --git a/module/program/tao.php b/module/program/tao.php index a1b462b84d..cba3ae607b 100644 --- a/module/program/tao.php +++ b/module/program/tao.php @@ -213,4 +213,151 @@ class programTao extends programModel ->fetchPairs(); if($shadowProducts) $this->dao->update(TABLE_PRODUCT)->set('program')->eq($newTopProgram)->where('id')->in($shadowProducts)->exec(); } + + /** + * 更新项目集的统计数据。 + * Update stats of program. + * + * @param array $projectIdList + * @access protected + * @return bool + */ + protected function updateStats(array $projectIdList): bool + { + /* Get summary and members of executions to be refreshed. */ + $stats = $this->getTaskStats($projectIdList); + $teamMembers = $this->dao->select('t1.root, COUNT(1) AS members')->from(TABLE_TEAM)->alias('t1') + ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account=t2.account') + ->where('t1.type')->eq('project') + ->beginIF(!empty($projectIdList))->andWhere('t1.root')->in($projectIdList)->fi() + ->andWhere('t2.deleted')->eq(0) + ->groupBy('t1.root') + ->fetchPairs('root'); + + foreach($teamMembers as $projectID => $teamCount) + { + if(!isset($stats[$projectID])) $stats[$projectID] = array('totalEstimate' => 0, 'totalConsumed' => 0, 'totalLeft' => 0, 'teamCount' => 0, 'totalConsumedNotDel' => 0, 'totalLeftNotDel' => 0); + $stats[$projectID]['teamCount'] = $teamCount; + } + + foreach($stats as $projectID => $project) + { + $totalRealNotDel = $project['totalConsumedNotDel'] + $project['totalLeftNotDel']; + $progress = $totalRealNotDel ? floor($project['totalConsumedNotDel'] / $totalRealNotDel * 1000) / 1000 * 100 : 0; + $this->dao->update(TABLE_PROJECT) + ->set('progress')->eq($progress) + ->set('teamCount')->eq($project['teamCount']) + ->set('estimate')->eq($project['totalEstimate']) + ->set('consumed')->eq($project['totalConsumedNotDel']) + ->set('left')->eq($project['totalLeftNotDel']) + ->where('id')->eq($projectID) + ->exec(); + } + + return !dao::isError(); + } + + /** + * 获取任务的统计数据。 + * Get task stats. + * + * @param array $projectIdList + * @access protected + * @return array + */ + protected function getTaskStats(array $projectIdList): array + { + /* 1. Get summary of executions to be refreshed. */ + $tasks = $this->dao->select('t1.id, execution, t1.estimate, t1.consumed, t1.`left`, t1.status')->from(TABLE_TASK)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id') + ->where('t1.deleted')->eq(0) + ->andWhere('t1.parent')->ge(0) // Ignore parent task. + ->beginIF(!empty($projectIdList))->andWhere('t1.project')->in($projectIdList)->fi() + ->fetchAll('id'); + + $summary = array(); + foreach($tasks as $task) + { + if(empty($task->execution)) continue; + if(!isset($summary[$task->execution])) + { + $summary[$task->execution] = new stdclass(); + $summary[$task->execution]->totalEstimate = 0; + $summary[$task->execution]->totalConsumed = 0; + $summary[$task->execution]->totalLeft = 0; + } + $summary[$task->execution]->execution = $task->execution; + $summary[$task->execution]->totalEstimate += $task->estimate; + $summary[$task->execution]->totalConsumed += $task->consumed; + $summary[$task->execution]->totalLeft += ($task->status == 'closed' or $task->status == 'cancel') ? 0 : $task->left; + } + + /* 2. Get all parents to be refreshed. */ + $executions = array(); + foreach($summary as $execution) $executions[$execution->execution] = $execution->execution; + $paths = $this->dao->select('id,path')->from(TABLE_PROJECT)->where('id')->in($executions)->fetchAll(); + $executionPaths = array(); + foreach($paths as $path) $executionPaths[$path->id] = explode(',', trim($path->path, ',')); + + /* 3. Compute stats of execution and parents. */ + $stats = array(); + $projectsPairs = $this->dao->select('id,deleted')->from(TABLE_PROJECT)->fetchPairs(); + foreach($summary as $execution) + { + $executionID = $execution->execution; + foreach($executionPaths[$executionID] as $nodeID) + { + if(!isset($stats[$nodeID])) $stats[$nodeID] = array('totalEstimate' => 0, 'totalConsumed' => 0, 'totalLeft' => 0, 'teamCount' => 0, 'totalLeftNotDel' => 0, 'totalConsumedNotDel' => 0); + $stats[$nodeID]['totalEstimate'] += $execution->totalEstimate; + $stats[$nodeID]['totalConsumed'] += $execution->totalConsumed; + $stats[$nodeID]['totalLeft'] += $execution->totalLeft; + + /* Check $execution->execution and $nodeID(path) is not deleted. */ + if(empty($projectsPairs[$execution->execution]) && empty($projectsPairs[$nodeID])) + { + $stats[$nodeID]['totalConsumedNotDel'] += $execution->totalConsumed; + $stats[$nodeID]['totalLeftNotDel'] += $execution->totalLeft; + } + } + } + return $stats; + } + + /** + * 更新项目集的进度。 + * Update program progress. + * + * @access protected + * @return bool + */ + protected function updateProcess(): bool + { + $projectList = $this->dao->select('id,progress,path,consumed,`left`')->from(TABLE_PROJECT) + ->where('type')->eq('project') + ->andWhere('parent')->ne(0) + ->andWhere('deleted')->eq(0) + ->fetchAll('id'); + + $programProgress = array(); + foreach($projectList as $projectID => $project) + { + $path = explode(',', trim($project->path, ',')); + foreach($path as $programID) + { + if($programID == $projectID) continue; + if(!isset($programProgress[$programID])) $programProgress[$programID] = array('consumed' => 0, 'left' => 0); + + $programProgress[$programID]['consumed'] += $project->consumed; + $programProgress[$programID]['left'] += $project->left; + } + } + + foreach($programProgress as $programID => $hours) + { + $progress = ($hours['consumed'] + $hours['left']) ? floor($hours['consumed'] / ($hours['consumed'] + $hours['left']) * 1000) / 1000 * 100 : 0; + $this->dao->update(TABLE_PROJECT)->set('progress')->eq($progress)->where('id')->eq($programID)->exec(); + } + + return !dao::isError(); + } }