| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- <?php
- /**
- * The tao file of program module of ZenTaoPMS.
- *
- * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
- * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
- * @author chen.tao<chentao@easycorp.ltd>
- * @package program
- * @link https://www.zentao.net
- */
- class programTao extends programModel
- {
- /**
- *获取所有根项目集基本数据。
- * Get base data of all root programs.
- *
- * @access protected
- * @return array
- */
- protected function getRootProgramList()
- {
- return $this->dao->select('id,name,PM,path,parent,type')
- ->from(TABLE_PROGRAM)
- ->where('parent')->eq('0')
- ->andWhere('deleted')->eq('0')
- ->andWhere('type')->eq('program')
- ->orderBy('order_asc')
- ->fetchAll();
- }
- /**
- * 构建项目视图的项目集的操作数据。
- * Build actions map for program.
- *
- * @param object $program
- * @access protected
- * @return array
- */
- protected function buildProgramActionsMap($program)
- {
- if($program->type == 'program' && (!$this->app->user->admin && strpos(",{$this->app->user->view->programs},", ",{$program->id},") === false)) return array();
- if($program->type == 'project') $this->loadModel('project');
- $modelClass = '';
- if($program->type == 'project') $modelClass = get_class($this->project);
- if($program->type == 'program') $modelClass = get_class($this);
- if($this->config->edition != 'open')
- {
- $this->loadModel('flow');
- $flowActions = $this->loadModel('workflowaction')->getList($program->type);
- }
- $actionsMap = array();
- $canStartProgram = common::hasPriv($program->type, 'start', $program);
- $canSuspendProgram = common::hasPriv($program->type, 'suspend', $program);
- $canCloseProgram = common::hasPriv($program->type, 'close', $program);
- $canActivateProgram = common::hasPriv($program->type, 'activate', $program);
- $normalActions = array('start', 'close', 'activate');
- foreach($normalActions as $action)
- {
- if($action == 'close' && (!$canCloseProgram || $program->status != 'doing')) continue;
- if($action == 'activate' && (!$canActivateProgram || $program->status != 'closed')) continue;
- if($action == 'start' && (!$canStartProgram || ($program->status != 'wait' && $program->status != 'suspended'))) continue;
- if($this->config->edition != 'open')
- {
- foreach($flowActions as $flowAction)
- {
- if($flowAction->action == $action && $flowAction->extensionType != 'none' && $flowAction->status == 'enable' && !empty($flowAction->conditions))
- {
- if(!$this->flow->checkConditions($flowAction->conditions, $data)) continue 2;
- }
- }
- }
- $item = new stdclass();
- $item->name = $action;
- $item->url = helper::createLink($program->type, $action, "programID={$program->id}");
- if($modelClass && !call_user_func_array(array($modelClass, 'isClickable'), array($program, $action))) $item->disabled = true;
- $actionsMap[] = $item;
- break;
- }
- if($canSuspendProgram || ($canCloseProgram && $program->status != 'doing') || ($canActivateProgram && $program->status != 'closed'))
- {
- $other = new stdclass();
- $other->name = 'other';
- $other->items = array();
- $otherActions = array('suspend', 'close', 'activate');
- foreach($otherActions as $action)
- {
- if(!common::hasPriv($program->type, $action, $program)) continue;
- if($action == 'close' && $program->status == 'doing') continue;
- if($this->config->edition != 'open')
- {
- foreach($flowActions as $flowAction)
- {
- if($flowAction->action == $action && $flowAction->extensionType != 'none' && $flowAction->status == 'enable' && !empty($flowAction->conditions))
- {
- if(!$this->flow->checkConditions($flowAction->conditions, $data)) continue 2;
- }
- }
- }
- $item = new stdclass();
- $item->name = $action;
- $item->url = helper::createLink($program->type, $action, "programID={$program->id}");
- if($modelClass && !call_user_func_array(array($modelClass, 'isClickable'), array($program, $action))) $item->disabled = true;
- if($action == 'close' && $program->status == 'closed') $item->hint = $this->lang->{$program->type}->tip->closed;
- if($action == 'activate' && $program->status == 'doing') $item->hint = $this->lang->{$program->type}->tip->actived;
- if($action == 'suspend' && $program->status == 'suspended') $item->hint = $this->lang->{$program->type}->tip->suspended;
- if($action == 'suspend' && $program->status == 'closed') $item->hint = $this->lang->{$program->type}->tip->notSuspend;
- $other->items[] = $item;
- }
- $actionsMap[] = $other;
- }
- return array_merge($actionsMap, $this->getNormalActions($program));
- }
- /**
- * 获取基础操作的按钮数据。
- * Get normal actions.
- *
- * @param object $program
- * @access protected
- * @return array
- */
- protected function getNormalActions($program)
- {
- $actionsMap = array();
- $normalActions = $program->type == 'project' ? array('edit') : array('edit', 'create', 'delete');
- foreach($normalActions as $action)
- {
- if(!common::hasPriv($program->type, $action)) continue;
- $item = new stdclass();
- $item->name = $action;
- if($action != 'delete') $item->url = helper::createLink($program->type, $action, "programID={$program->id}");
- if($action == 'delete') $item->url = "javascript:confirmDelete({$program->id}, 'program', '{$program->name}')";
- if($action == 'create' && $program->status == 'closed')
- {
- $item->disabled = true;
- $item->hint = $this->lang->program->tip->notCreate;
- }
- $actionsMap[] = $item;
- }
- return $actionsMap;
- }
- /**
- * 构建项目视角中项目的操作数据。
- * Build actions map for project.
- *
- * @param object $project
- * @access protected
- * @return array
- */
- protected function buildProjectActionsMap($project)
- {
- $this->loadModel('project');
- $actionsMap = array();
- if(common::hasPriv('project', 'team'))
- {
- $item = new stdclass();
- $item->name = 'team';
- $item->url = helper::createLink('project', 'team', "projectID={$project->id}");
- $actionsMap[] = $item;
- }
- if(common::hasPriv('project', 'manageProducts') || common::hasPriv('project', 'whitelist') || common::hasPriv('project', 'delete'))
- {
- $more = new stdclass();
- $more->name = 'more';
- $more->items = array();
- $moreActions = array('group', 'manageProducts', 'whitelist', 'delete');
- foreach($moreActions as $action)
- {
- if(!common::hasPriv('project', $action)) continue;
- $item = new stdclass();
- $item->name = $action == 'manageProducts' ? 'link' : $action;
- if($action != 'delete') $item->url = helper::createLink('project', $action, "projectID={$project->id}");
- if($action == 'delete') $item->url = "javascript:confirmDelete({$project->id}, 'project', '{$project->name}')";
- if($action == 'whitelist' and $project->acl == 'open')
- {
- $item->disabled = true;
- $item->hint = $this->lang->project->tip->whitelist;
- }
- if($action == 'group' && $project->model == 'kanban')
- {
- $item->disabled = true;
- $item->hint = $this->lang->project->tip->group;
- }
- if(!call_user_func_array(array($this->project, 'isClickable'), array($project, $item->name))) $item->disabled = true;
- $more->items[] = $item;
- }
- $actionsMap[] = $more;
- }
- return $actionsMap;
- }
- /**
- * 如果修改了父项目集,修改关联的产品。
- * If change parent, then fix linked product.
- *
- * @param int $programID
- * @param int $parent
- * @param int $oldParent
- * @param string $oldPath
- * @access protected
- * @return void
- */
- protected function fixLinkedProduct($programID, $parent, $oldParent, $oldPath)
- {
- if($parent == $oldParent) return;
- /* Move product to new top program. */
- $oldTopProgram = $this->getTopByPath($oldPath);
- $newTopProgram = $this->getTopByID($programID);
- if($oldTopProgram == $newTopProgram) return;
- if($oldParent == 0)
- {
- $this->dao->update(TABLE_PRODUCT)->set('program')->eq($newTopProgram)->where('program')->eq($oldTopProgram)->exec();
- return;
- }
- /* Get the shadow products that produced by the program's no product projects. */
- $shadowProducts = $this->dao->select('t1.id')->from(TABLE_PRODUCT)->alias('t1')
- ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.id=t2.product')
- ->leftJoin(TABLE_PROJECT)->alias('t3')->on('t2.project=t3.id')
- ->where('t3.path')->like("%,$programID,%")
- ->andWhere('t3.type')->eq('project')
- ->andWhere('t3.hasProduct')->eq('0')
- ->andWhere('t1.shadow')->eq('1')
- ->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($projectIdList)
- {
- /* 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($projectIdList)
- {
- /* 1. Set execution has no tasks. */
- $summary = $this->setNoTaskExecution($projectIdList);
- /* 2. 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.execution = t2.id')
- ->where('t1.deleted')->eq(0)
- ->andWhere('t1.isParent')->eq('0') // Ignore parent task.
- ->andWhere('t2.deleted')->eq(0)
- ->beginIF(!empty($projectIdList))->andWhere('t2.project')->in($projectIdList)->fi()
- ->fetchAll('id');
- 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;
- }
- /* 3. 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, ','));
- /* 4. 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;
- if(!isset($executionPaths[$executionID])) continue;
- 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;
- }
- /**
- * 设置没有任务的执行数据。
- * Set execution stat when has no task.
- *
- * @param array $projectIdList
- * @access protected
- * @return array
- */
- protected function setNoTaskExecution($projectIdList)
- {
- $summary = array();
- $executionGroup = $this->dao->select('id, project')->from(TABLE_PROJECT)->where('project')->in($projectIdList)->andWhere('deleted')->eq(0)->fetchGroup('project', 'id');
- foreach($projectIdList as $projectID)
- {
- $executions = zget($executionGroup, $projectID, array());
- foreach($executions as $executionID => $execution)
- {
- $taskCount = $this->dao->select('id')->from(TABLE_TASK)
- ->where('deleted')->eq(0)
- ->andWhere('execution')->eq($executionID)
- ->count();
- if(empty($taskCount))
- {
- $summary[$executionID] = new stdclass();
- $summary[$executionID]->totalEstimate = 0;
- $summary[$executionID]->totalConsumed = 0;
- $summary[$executionID]->totalLeft = 0;
- $summary[$executionID]->execution = $executionID;
- }
- }
- }
- return $summary;
- }
- /**
- * 更新项目集的进度。
- * Update program progress.
- *
- * @access protected
- * @return bool
- */
- protected function updateProgress()
- {
- $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)
- {
- $hours['progress'] = ($hours['consumed'] + $hours['left']) ? floor($hours['consumed'] / ($hours['consumed'] + $hours['left']) * 1000) / 1000 * 100 : 0;
- $this->dao->update(TABLE_PROJECT)->data($hours)->where('id')->eq($programID)->exec();
- }
- return !dao::isError();
- }
- }
|