| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964 |
- <?php
- /**
- * The model file of programplan 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 Chunsheng Wang <chunsheng@cnezsoft.com>
- * @package programplan
- * @link https://www.zentao.net
- */
- class programplanModel extends model
- {
- /**
- * 根据id获取阶段。
- * Get plan by id.
- *
- * @param int $planID
- * @access public
- * @return object|false
- */
- public function getByID($planID)
- {
- $plan = $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->eq($planID)->fetch();
- if(empty($plan)) return false;
- return $this->processPlan($plan);
- }
- /**
- * 获取阶段列表。
- * Get stages 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(common::isTutorialMode()) return $this->loadModel('tutorial')->getExecutionStats();
- $plans = $this->programplanTao->getStageList($executionID, $productID, $browseType, $orderBy);
- return $this->processPlans($plans);
- }
- /**
- * 根据id 查询项目列表。
- * Get project 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);
- if(!$plans) return array();
- $parents = array();
- $children = array();
- foreach($plans as $planID => $plan)
- {
- if($plan->grade == 1) $parents[$planID] = $plan;
- if($plan->grade > 1) $children[$plan->parent][] = $plan;
- }
- foreach($parents as $planID => $plan) $parents[$planID]->children = isset($children[$planID]) ? $children[$planID] : array();
- return $parents;
- }
- /**
- * 获取项目中的阶段数据键值对。
- * Get stade pairs for project.
- *
- * @param int $executionID
- * @param int $productID
- * @param string $type all|leaf
- * @access public
- * @return array
- */
- public function getPairs($executionID, $productID = 0, $type = 'all')
- {
- $plans = $this->getStage($executionID, $productID, $type);
- $parents = array();
- $pairs = array();
- if(strpos($type, 'leaf') !== false) array_map(function($plan) use(&$parents){$parents[$plan->parent] = true;}, $plans);
- foreach($plans as $planID => $plan)
- {
- if(strpos($type, 'leaf') !== false and isset($parents[$plan->id])) continue;
- $paths = array_slice(explode(',', trim($plan->path, ',')), 1);
- $planName = '';
- foreach($paths as $path)
- {
- if(isset($plans[$path])) $planName .= '/' . $plans[$path]->name;
- }
- $pairs[$planID] = $planName;
- }
- return $pairs;
- }
- /**
- * 获取甘特图页面数据。
- * Get gantt data.
- *
- * @param int $projectID
- * @param int $productID
- * @param int $baselineID
- * @param string $selectCustom
- * @param bool $returnJson
- * @param string $browseType
- * @param int $queryID
- * @access public
- * @return string|array
- */
- public function getDataForGantt($projectID, $productID, $baselineID = 0, $selectCustom = '', $returnJson = true, $browseType = '', $queryID = 0)
- {
- $plans = $this->getStage($projectID, $productID, 'all', 'order');
- $project = $this->loadModel('project')->getById($projectID);
- /* Set plan baseline data. */
- if($baselineID)
- {
- $baseline = $this->loadModel('cm')->getByID($baselineID);
- $oldData = json_decode($baseline->data);
- $plans = $this->programplanTao->setPlanBaseline((array)$oldData->stage, $plans);
- }
- /* Set task baseline data. */
- $tasks = $this->getGanttTasks($projectID, array_keys($plans), $browseType, $queryID);
- if($baselineID) $this->programplanTao->setTaskBaseline(isset($oldData->task) ? $oldData->task : array(), $tasks); // Set task baseline.
- if($browseType == 'bysearch')
- {
- $taskExecutions = array_column($tasks, 'execution');
- $plans = array_filter($plans, function($plan) use($taskExecutions) {return in_array($plan->id, $taskExecutions);});
- }
- /* Set plan for gantt view. */
- $result = $this->programplanTao->initGanttPlans($plans, $browseType);
- $datas = $result['datas'];
- $planIdList = $result['planIdList'];
- $stageIndex = $result['stageIndex'];
- $reviewDeadline = $result['reviewDeadline'];
- /* Judge whether to display tasks under the stage. */
- if(empty($selectCustom)) $selectCustom = $this->loadModel('setting')->getItem("owner={$this->app->user->account}&module=programplan§ion=browse&key=stageCustom");
- /* Set task for gantt view. */
- $result = $this->programplanTao->setTask($tasks, $plans, $selectCustom, $datas, $stageIndex);
- $datas = $result['datas'];
- $stageIndex = $result['stageIndex'];
- /* Build data for ipd. */
- if($project->model == 'ipd' and $datas) $datas = $this->programplanTao->buildGanttData4IPD($datas, $projectID, $productID, $selectCustom, $reviewDeadline);
- /* Calculate the progress of the phase. */
- $datas = $this->programplanTao->setStageSummary($datas, $stageIndex);
- foreach($tasks as $task) $task->id = $task->execution . '-' . $task->id;
- /* Set relation task data. */
- $datas['links'] = $this->programplanTao->buildGanttLinks($projectID, $tasks);
- $datas['data'] = isset($datas['data']) ? array_values($datas['data']) : array();
- return $returnJson ? json_encode($datas) : $datas;
- }
- /**
- * 获取按照指派给分组甘特图相关数据。
- * Gets Gantt chart related data as assigned to the group.
- *
- * @param int $executionID
- * @param int $productID
- * @param int $baselineID
- * @param string $selectCustom
- * @param bool $returnJson
- * @param string $browseType
- * @param int $queryID
- * @access public
- * @return string|array
- */
- public function getDataForGanttGroupByAssignedTo($executionID, $productID, $baselineID = 0, $selectCustom = '', $returnJson = true, $browseType = '', $queryID = 0)
- {
- $datas = array();
- $stageIndex = array();
- $plans = $this->getStage($executionID, $productID);
- $planIdList = array_column($plans, 'id');
- $users = $this->loadModel('user')->getPairs('noletter');
- $tasks = $this->getGanttTasks($executionID, $planIdList, $browseType, $queryID);
- $tasksGroup = $this->programplanTao->buildTaskGroup($tasks);
- /* Judge whether to display tasks under the stage. */
- if(empty($selectCustom)) $selectCustom = $this->loadModel('setting')->getItem("owner={$this->app->user->account}&module=programplan§ion=browse&key=stageCustom");
- $begin = $end = helper::today();
- $deadlineList = array();
- foreach($tasksGroup as $group => $tasks)
- {
- foreach($tasks as $taskID => $task)
- {
- $deadline = helper::isZeroDate($task->deadline) && !empty($plans[$task->execution]->end) ? $plans[$task->execution]->end : $task->deadline;
- if(helper::isZeroDate($deadline)) continue;
- $begin = $deadline < $begin ? $deadline : $begin;
- $deadlineList[$taskID] = $deadline;
- }
- }
- $groupID = 0;
- $datas['data'] = array();
- $workingDays = $this->loadModel('holiday')->getActualWorkingDays($begin, $end);
- foreach($tasksGroup as $group => $tasks)
- {
- if(!$group) $group = '/'; // 未指派
- $groupID ++;
- $groupKey = $groupID . $group;
- $datas['data'][$groupKey] = $this->programplanTao->buildGroupDataForGantt($groupID, $group, $users);
- $realStartDate = array();
- $realEndDate = array();
- $totalTask = count($tasks);
- foreach($tasks as $taskID => $task)
- {
- $dateLimit = $this->programplanTao->getTaskDateLimit($task, zget($plans, $task->execution, null));
- if(strpos($selectCustom, 'task') !== false)
- {
- $data = $this->programplanTao->buildTaskDataForGantt($task, $dateLimit, $groupID, $tasks);
- $data->id = $groupID . '-' . $task->id;
- $data->parent = $task->parent > 0 && isset($tasks[$task->parent]) ? $groupID . '-' . $task->parent : $groupID;
- /* Delayed or not?. */
- $isNotCancel = !in_array($task->status, array('cancel', 'closed')) || ($task->status == 'closed' && !helper::isZeroDate($task->finishedDate) && $task->closedReason != 'cancel');
- $isComputeDelay = $isNotCancel && !empty($deadlineList[$taskID]);
- if($isComputeDelay) $task = $this->task->computeDelay($task, $deadlineList[$taskID], $workingDays);
- $data->delay = $this->lang->programplan->delayList[0];
- $data->delayDays = 0;
- if(isset($task->delay) && $task->delay > 0)
- {
- $data->delay = $this->lang->programplan->delayList[1];
- $data->delayDays = $task->delay;
- }
- $datas['data'][$task->id] = $data;
- }
- if(!empty($dateLimit['start'])) $realStartDate[] = strtotime($dateLimit['start']);
- if(!empty($dateLimit['end'])) $realEndDate[] = strtotime($dateLimit['end']);
- if(!isset($stageIndex[$groupKey]['totalConsumed'])) $stageIndex[$groupKey]['totalConsumed'] = 0;
- if(!isset($stageIndex[$groupKey]['totalReal'])) $stageIndex[$groupKey]['totalReal'] = 0;
- if(!isset($stageIndex[$groupKey]['totalEstimate'])) $stageIndex[$groupKey]['totalEstimate'] = 0;
- $stageIndex[$groupKey]['totalConsumed'] += $task->consumed;
- $stageIndex[$groupKey]['totalReal'] += $task->left + $task->consumed;
- $stageIndex[$groupKey]['totalEstimate'] += $task->estimate;
- }
- /* Calculate group realBegan and realEnd. */
- if(!empty($realStartDate)) $datas['data'][$groupKey]->realBegan = date('Y-m-d', min($realStartDate));
- if(!empty($realEndDate) and (count($realEndDate) == $totalTask)) $datas['data'][$groupKey]->realEnd = date('Y-m-d', max($realEndDate));
- }
- $datas = $this->programplanTao->setStageSummary($datas, $stageIndex);
- $datas['links'] = $this->programplanTao->buildGanttLinks($executionID, $datas['data']);
- $datas['data'] = isset($datas['data']) ? array_values($datas['data']) : array();
- return $returnJson ? json_encode($datas) : $datas;
- }
- /**
- * 批量查询阶段关联的项目和属性并过滤日期。
- * Get product and attribute for stage correlation.
- *
- * @param array $plans
- * @access public
- * @return array
- */
- public function processPlans($plans)
- {
- foreach($plans as $planID => $plan) $plans[$planID] = $this->processPlan($plan);
- return $plans;
- }
- /**
- * 查询阶段关联的项目和属性并过滤日期。
- * Get product and attribute for stage correlation.
- *
- * @param object $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->programplanTao->getStageCount($plan->id, 'milestone');
- if($milestones > 0)
- {
- $plan->milestone = 0;
- $plan->setMilestone = false;
- }
- }
- $plan->begin = helper::isZeroDate($plan->begin) ? '' : $plan->begin;
- $plan->end = helper::isZeroDate($plan->end) ? '' : $plan->end;
- $plan->realBegan = helper::isZeroDate($plan->realBegan) ? '' : $plan->realBegan;
- $plan->realEnd = helper::isZeroDate($plan->realEnd) ? '' : $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 string $begin
- * @param string $end
- * @access protected
- * @return int
- */
- protected function getDuration($begin, $end)
- {
- $duration = $this->loadModel('holiday')->getActualWorkingDays($begin, $end);
- return count($duration);
- }
- /**
- * 创建/设置一个项目阶段。
- * Create/Set a project plan/phase.
- *
- * @param array $plans
- * @param int $projectID
- * @param int $productID
- * @param int $parentID
- * @param int $totalSyncData
- * @access public
- * @return bool
- */
- public function create($plans, $projectID = 0, $productID = 0, $parentID = 0, $totalSyncData = 0)
- {
- if(empty($plans)) dao::$errors['message'][] = sprintf($this->lang->error->notempty, $this->lang->programplan->name);
- if(dao::isError()) return false;
- /* Get linked product by projectID. */
- $this->loadModel('action');
- $this->loadModel('execution');
- $linkProducts = $this->programplanTao->getLinkProductsForCreate($projectID, $productID);
- $project = $this->fetchByID($projectID, 'project');
- /* Set each plans. */
- $updateUserViewIdList = array();
- $enabledPoints = array();
- $parallel = 0;
- $parents = array();
- $prevSyncData = null;
- $prevLevel = 0;
- foreach($plans as $plan)
- {
- $level = isset($plan->level) ? $plan->level : 0;
- $syncData = isset($plan->syncData) ? $plan->syncData : null;
- unset($plan->level, $plan->syncData);
- $parallel = isset($plan->parallel) ? $plan->parallel : 0;
- if(!empty($plan->point)) $enabledPoints = array_merge($enabledPoints, $plan->point);
- if($plan->id)
- {
- $stageID = $plan->id;
- $parents[$level] = $stageID;
- unset($plan->id, $plan->type);
- $changes = $this->programplanTao->updateRow($stageID, $projectID, $plan);
- if(dao::isError()) return false;
- if(!empty($changes))
- {
- $actionID = $this->action->create('execution', $stageID, 'edited');
- $this->action->logHistory($actionID, $changes);
- /* Add PM to stage teams and project teams. */
- if(!empty($plan->PM)) $this->execution->addExecutionMembers($stageID, array($plan->PM));
- if($plan->acl != 'open') $updateUserViewIdList[] = $stageID;
- $this->updateSubStageAttr($stageID, $plan->attribute);
- }
- }
- else
- {
- if($level > 0 && isset($parents[$level - 1])) $plan->parent = $parents[$level - 1];
- $stageID = $this->programplanTao->insertStage($plan, $projectID, $productID, $level > 0 ? $plan->parent : $parentID);
- if(dao::isError()) return false;
- $parents[$level] = $stageID;
- $extra = ($project && $project->hasProduct and !empty($linkProducts['products'])) ? implode(',', $linkProducts['products']) : '';
- $this->action->create('execution', $stageID, 'opened', '', $extra);
- $this->execution->updateProducts($stageID, $linkProducts);
- if($plan->acl != 'open') $updateUserViewIdList[] = $stageID;
- }
- if(!$totalSyncData && $prevSyncData && $prevLevel == $level - 1) $this->programplanTao->syncParentData($stageID, $parents[$prevLevel]);
- if($totalSyncData && $prevSyncData === null && $parentID) $this->programplanTao->syncParentData($stageID, $parentID);
- $prevSyncData = $syncData;
- $prevLevel = $level;
- }
- if($project && $project->model == 'ipd') $this->dao->update(TABLE_PROJECT)->set('parallel')->eq($parallel)->where('id')->eq($projectID)->exec();
- if($updateUserViewIdList) $this->loadModel('user')->updateUserView($updateUserViewIdList, 'sprint');
- if($enabledPoints) $this->programplanTao->updatePoint($projectID, $enabledPoints);
- return true;
- }
- /**
- * 设置阶段在层级中路径。
- * 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();
- $this->loadModel('execution');
- if(empty($parent))
- {
- $path['path'] = ",{$stage->id},";
- $path['grade'] = 1;
- }
- elseif($parent && $parent->type == 'project')
- {
- $path['path'] = ",{$parent->id},{$stage->id},";
- $path['grade'] = 1;
- }
- elseif(isset($this->lang->execution->typeList[$parent->type]))
- {
- $path['path'] = $parent->path . "{$stage->id},";
- $path['grade'] = $parent->grade + 1;
- }
- $children = $this->execution->getChildExecutions($planID);
- $this->dao->update(TABLE_PROJECT)->set('path')->eq($path['path'])->set('grade')->eq($path['grade'])->where('id')->eq($stage->id)->exec();
- if(empty($children)) return !dao::isError();
- foreach($children as $id => $child) $this->setTreePath($id);
- return !dao::isError();
- }
- /**
- * 更新阶段。
- * Update a plan.
- *
- * @param int $planID
- * @param int $projectID
- * @param object $plan
- * @access public
- * @return bool
- */
- public function update($planID = 0, $projectID = 0, $plan = null)
- {
- if(empty($plan)) return false;
- $changes = $this->programplanTao->updateRow($planID, $projectID, $plan);
- if(dao::isError()) return false;
- /* Synchronously update sub-phase permissions. */
- $childIdList = $this->dao->select('id')->from(TABLE_PROJECT)->where('path')->like("%,$planID,%")->fetchPairs();
- if(!empty($childIdList)) $this->dao->update(TABLE_PROJECT)->set('acl')->eq($plan->acl)->where('id')->in($childIdList)->exec();
- $this->setTreePath($planID);
- $this->updateSubStageAttr($planID, $plan->attribute);
- if($plan->acl != 'open')
- {
- $this->loadModel('user')->updateUserView($childIdList, 'sprint');
- }
- if($changes)
- {
- $actionID = $this->loadModel('action')->create('execution', $planID, 'edited');
- $this->action->logHistory($actionID, $changes);
- }
- return true;
- }
- /**
- * 根据计划ID判断是否创建了任务。
- * Is create task.
- *
- * @param int $planID
- * @access public
- * @return bool
- */
- public function isCreateTask($planID)
- {
- if(empty($planID)) return true;
- $task = $this->dao->select('*')->from(TABLE_TASK)->where('execution')->eq($planID)->andWhere('deleted')->eq('0')->limit(1)->fetch();
- return empty($task);
- }
- /**
- * 根据父id获取父阶段的子类型。
- * Get parent stage's children types by parentID.
- *
- * @param int $parentID
- * @access public
- * @return array|bool
- */
- public function getParentChildrenTypes($parentID)
- {
- if(empty($parentID)) return true;
- return $this->dao->select('type')->from(TABLE_EXECUTION)->where('parent')->eq($parentID)->andWhere('deleted')->eq('0')->fetchPairs();
- }
- /**
- * 是否可以点击.
- * Is clickable.
- *
- * @param object $plan
- * @param string $action
- * @static
- * @access public
- * @return bool
- */
- public static function isClickable($plan, $action)
- {
- if(strtolower($action) != 'create') return true;
- global $dao;
- if(empty($plan->id)) return true;
- $task = $dao->select('*')->from(TABLE_TASK)->where('execution')->eq($plan->id)->andWhere('deleted')->eq('0')->limit(1)->fetch();
- return empty($task);
- }
- /**
- * 获取父阶段列表。
- * Get parent stage list.
- *
- * @param int $executionID
- * @param int $planID
- * @param int $productID
- * @param string $param withParent|noclosed
- * @access public
- * @return array
- */
- public function getParentStageList($executionID, $planID, $productID, $param = '')
- {
- $parentStage = $this->programplanTao->getParentStages($executionID, $planID, $productID, $param);
- if(!$parentStage) return array(0 => $this->lang->programplan->emptyParent);
- $plan = $this->getByID($planID);
- $parents = array();
- $withParent = strpos($param, 'withparent') !== false;
- $isStage = strpos("|$param|", '|stage|') !== false || strpos($param, 'stage') === false;
- $allExecutions = $withParent ? $this->dao->select('id,name,parent,grade,path,type')->from(TABLE_EXECUTION)
- ->where('type')->notin(array('program', 'project'))
- ->andWhere('deleted')->eq('0')
- ->beginIf($executionID)->andWhere('project')->eq($executionID)->fi()
- ->fetchAll('id') : array();
- foreach($allExecutions as $execution) $parents[$execution->parent] = isset($allExecutions[$execution->parent]) ? $allExecutions[$execution->parent] : array();
- foreach($parentStage as $key => $stage)
- {
- $isCreate = $this->isCreateTask($key);
- $parentTypes = $this->getParentChildrenTypes($key);
- if(!empty($plan))
- {
- if(!$isCreate && $key != $plan->parent) unset($parentStage[$key]);
- if($plan->type == 'stage' && (isset($parentTypes['sprint']) || isset($parentTypes['kanban']))) unset($parentStage[$key]);
- if(($plan->type == 'sprint' || $plan->type == 'kanban') && isset($parentTypes['stage'])) unset($parentStage[$key]);
- }
- else
- {
- if(!$isCreate) unset($parentStage[$key]); // 隐藏有数据的阶段
- if($isStage && (isset($parentTypes['sprint']) || isset($parentTypes['kanban']))) unset($parentStage[$key]); // 如果是阶段,隐藏叶子节点是迭代和看板的数据
- if(!$isStage && (isset($parentTypes['stage']) || isset($parentTypes['stage']))) unset($parentStage[$key]); // 如果不是阶段,隐藏叶子节点是阶段的数据
- }
- /* Set stage name. */
- if($withParent && isset($parentStage[$key]) && !empty($allExecutions))
- {
- $currentStage = $allExecutions[$key];
- $paths = array_slice(explode(',', trim($currentStage->path, ',')), 1);
- $executionName = '';
- foreach($paths as $path)
- {
- if(isset($allExecutions[$path])) $executionName .= '/' . $allExecutions[$path]->name;
- }
- $parentStage[$key] = $executionName;
- }
- }
- $project = $this->fetchByID($executionID);
- if((!empty($plan) && $plan->type == 'stage') || $project->model == 'waterfall' || $isStage) $parentStage[0] = $this->lang->programplan->emptyParent;
- ksort($parentStage);
- return $parentStage;
- }
- /**
- * 通过计算获取阶段状态。
- * Compute stage status.
- *
- * @param int $stage
- * @param string $action
- * @param bool $isParent
- * @access public
- * @return bool|array
- * @param int $stageID
- */
- public function computeProgress($stageID, $action = '', $isParent = false)
- {
- $stage = $this->loadModel('execution')->fetchByID($stageID);
- if(empty($stage) || empty($stage->path)) return false;
- $project = $this->loadModel('project')->fetchByID($stage->project);
- $model = zget($project, 'model', '');
- if(empty($stage) or empty($stage->path) or (!in_array($model, array('waterfall','waterfallplus','ipd','research')))) return false;
- $action = strtolower($action);
- $parentIdList = array_reverse(explode(',', trim($stage->path, ',')));
- foreach($parentIdList as $id)
- {
- $parent = $this->execution->fetchByID((int)$id);
- if(empty($this->lang->execution->typeList[$parent->type]) || (!$isParent && $id == $stageID)) continue;
- /** 获取子阶段关联开始任务数以及状态下子阶段数量。 */
- /** Get the number of sub-stage associated start tasks and the number of sub-stages under the state. */
- $statusCount = array();
- $children = $this->execution->getChildExecutions($parent->id);
- $allChildren = $this->dao->select('id')->from(TABLE_EXECUTION)->where('deleted')->eq(0)->andWhere('path')->like("{$parent->path}%")->andWhere('id')->ne($id)->fetchPairs();
- $startTasks = $this->dao->select('count(1) as count')->from(TABLE_TASK)->where('deleted')->eq(0)->andWhere('execution')->in($allChildren)->andWhere('consumed')->ne(0)->fetch('count');
- foreach($children as $childExecution)
- {
- if(empty($statusCount[$childExecution->status])) $statusCount[$childExecution->status] = 0;
- $statusCount[$childExecution->status] ++;
- }
- if(empty($statusCount)) continue;
- $result = $this->getNewParentAndAction($statusCount, $parent, (int)$startTasks, $action, $project);
- $newParent = $result['newParent'] ?? null;
- $parentAction = $result['parentAction'] ?? '';
- /* 如果当前是顶级阶段,并且由于交付物不能关闭,则跳转到顶级阶段的关闭页面。 */
- if(isset($newParent->status) && $newParent->status == 'closed')
- {
- $isTopStage = $parent->grade == 1 && $parent->type != 'project' && $stageID != $id && $parent->status == 'doing';
- if(in_array($this->config->edition, array('max', 'ipd')) && $isTopStage && !$this->execution->canCloseByDeliverable($parent))
- {
- $url = helper::createLink('execution', 'close', "executionID={$parent->id}");
- return array('result' => 'fail', 'callback' => "zui.Modal.confirm('{$this->lang->execution->cannotAutoCloseParent}').then((res) => {if(res) {loadModal('$url', '.modal-dialog');} else {loadPage();}});");
- }
- }
- /** 更新状态以及记录日志。 */
- /** Update status and save log. */
- if(isset($newParent) && $newParent)
- {
- $this->dao->update(TABLE_EXECUTION)->data($newParent)->where('id')->eq($id)->exec();
- $this->loadModel('action')->create('execution', (int)$id, $parentAction, '', $parentAction);
- }
- unset($newParent, $parentAction);
- }
- return true;
- }
- /**
- * 根据阶段ID,检查阶段是否是叶子阶段。
- * Check if the stage is a leaf stage.
- *
- * @param int $stageID
- * @access public
- * @return bool
- */
- public function checkLeafStage($stageID)
- {
- if(empty($stageID)) return false;
- $subStageNumbers = $this->dao->select('COUNT(`id`) AS total')->from(TABLE_EXECUTION)
- ->where('parent')->eq($stageID)
- ->andWhere('deleted')->eq(0)
- ->fetch('total');
- return $subStageNumbers == 0;
- }
- /**
- * 检查是否为顶级。
- * Check whether it is the top stage.
- *
- * @param int $planID
- * @access public
- * @return bool
- */
- public function isTopStage($planID)
- {
- $parentID = $this->dao->select('parent')->from(TABLE_EXECUTION)->where('id')->eq($planID)->fetch('parent');
- $parentType = $this->dao->select('type')->from(TABLE_EXECUTION)->where('id')->eq($parentID)->fetch('type');
- return $parentType == 'project';
- }
- /**
- * 更新子阶段的属性值.
- * Update sub-stage attribute.
- *
- * @param int $planID
- * @param string $attribute
- * @access public
- * @return true
- */
- public function updateSubStageAttr($planID, $attribute)
- {
- if($attribute == 'mix') return true;
- $subStageList = $this->dao->select('id')->from(TABLE_EXECUTION)->where('parent')->eq($planID)->andWhere('deleted')->eq(0)->fetchAll('id');
- if(empty($subStageList)) return true;
- $this->dao->update(TABLE_EXECUTION)->set('attribute')->eq($attribute)->where('id')->in(array_keys($subStageList))->exec();
- foreach($subStageList as $childID => $subStage) $this->updateSubStageAttr($childID, $attribute);
- return true;
- }
- /**
- * 获取阶段当前和子集信息。
- * Get plan and its children.
- *
- * @param string|int|array $planIdList
- * @access public
- * @return array
- */
- public function getSelfAndChildrenList($planIdList)
- {
- if(is_numeric($planIdList)) $planIdList = (array)$planIdList;
- $planList = $this->dao->select('t2.*')->from(TABLE_EXECUTION)->alias('t1')
- ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('FIND_IN_SET(t1.id,t2.`path`)')
- ->where('t1.id')->in($planIdList)
- ->andWhere('t2.deleted')->eq(0)
- ->fetchAll('id');
- $selfAndChildrenList = array();
- foreach($planIdList as $planID)
- {
- if(!isset($selfAndChildrenList[$planID])) $selfAndChildrenList[$planID] = array();
- foreach($planList as $plan)
- {
- if(strpos($plan->path, ",$planID,") !== false) $selfAndChildrenList[$planID][$plan->id] = $plan;
- }
- }
- return $selfAndChildrenList;
- }
- /**
- * 获取阶段同一层级信息。
- * Get plan's siblings.
- *
- * @param string|int|array $planIdList
- * @access public
- * @return array
- */
- public function getSiblings($planIdList)
- {
- if(is_numeric($planIdList)) $planIdList = (array)$planIdList;
- $siblingsList = $this->dao->select('t1.*')->from(TABLE_EXECUTION)->alias('t1')
- ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.parent=t2.parent')
- ->where('t2.id')->in($planIdList)
- ->andWhere('t1.deleted')->eq(0)
- ->fetchAll('id');
- $siblingStages = array();
- foreach($planIdList as $planID)
- {
- if(!isset($siblingStages[$planID])) $siblingStages[$planID] = array();
- foreach($siblingsList as $sibling)
- {
- if($siblingsList[$planID]->parent == $sibling->parent) $siblingStages[$planID][$sibling->id] = $sibling;
- }
- }
- return $siblingStages;
- }
- /**
- * 获取阶段ID的属性。
- * Get stageID attribute.
- *
- * @param int $stageID
- * @access public
- * @return false|string
- */
- public function getStageAttribute($stageID)
- {
- return $this->dao->select('attribute')->from(TABLE_EXECUTION)->where('id')->eq($stageID)->fetch('attribute');
- }
- /**
- * 保存自定义配置
- * Save custom setting.
- *
- * @param object $settings
- * @param string $owner
- * @param string $module
- * @access protected
- * @return void
- */
- protected function saveCustomSetting($settings, $owner, $module)
- {
- $zooming = zget($settings, 'zooming', '');
- $stageCustom = zget($settings, 'stageCustom', '');
- $ganttFields = zget($settings, 'ganttFields', '');
- $this->loadModel('setting');
- $this->setting->setItem("$owner.$module.browse.stageCustom", $stageCustom);
- $this->setting->setItem("$owner.$module.ganttCustom.ganttFields", $ganttFields);
- $this->setting->setItem("$owner.$module.ganttCustom.zooming", $zooming);
- }
- /**
- * 获取甘特图的任务.
- * Get tasks in gantt.
- *
- * @param int $projectID
- * @param array $planIdList
- * @param string $browseType
- * @param int $queryID
- * @param object $pager
- * @access public
- * @return array
- */
- public function getGanttTasks($projectID, $planIdList, $browseType, $queryID, $pager = null)
- {
- $tasks = array();
- if($browseType == 'bysearch')
- {
- $query = $this->loadModel('search')->getQuery($queryID);
- if($query)
- {
- $this->session->set('projectTaskQuery', $query->sql);
- $this->session->set('projectTaskForm', $query->form);
- }
- elseif(!$this->session->projectTaskQuery)
- {
- $this->session->set('projectTaskQuery', ' 1 = 1');
- }
- if(strpos($this->session->projectTaskQuery, "deleted =") === false) $this->session->set('projectTaskQuery', $this->session->projectTaskQuery . " AND deleted = '0'");
- $projectTaskQuery = $this->session->projectTaskQuery;
- $projectTaskQuery .= " AND `project` = '$projectID'";
- $projectTaskQuery .= " AND `execution` " . helper::dbIN($planIdList);
- $this->session->set('projectTaskQueryCondition', $projectTaskQuery, $this->app->tab);
- $this->session->set('projectTaskOnlyCondition', true, $this->app->tab);
- $tasks = $this->loadModel('execution')->getSearchTasks($projectTaskQuery, 'execution_asc,order_asc,id_asc', $pager, 'projectTask');
- }
- elseif(!empty($planIdList))
- {
- $tasks = $this->dao->select('t1.*,t2.version AS latestStoryVersion, t2.status AS storyStatus')->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
- ->where('t1.deleted')->eq(0)
- ->andWhere('t1.project')->eq($projectID)
- ->andWhere('t1.execution')->in($planIdList)
- ->orderBy('execution_asc, order_asc, id_asc')
- ->fetchAll('id');
- }
- $isGantt = $this->app->rawModule == 'programplan' && $this->app->rawMethod == 'browse';
- if($isGantt) $plans = $this->loadModel('execution')->getByIdList($planIdList);
- $begin = $end = helper::today();
- $deadlineList = array();
- $taskDateLimit = $this->dao->select('taskDateLimit')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch('taskDateLimit');
- foreach($tasks as $taskID => $task)
- {
- if(!$isGantt && helper::isZeroDate($task->deadline)) continue;
- $plan = isset($plans[$task->execution]) ? $plans[$task->execution] : null;
- $dateLimit = $this->programplanTao->getTaskDateLimit($task, $plan, $taskDateLimit == 'limit' ? zget($tasks, $task->parent, null) : null);
- $deadline = substr($dateLimit['end'], 0, 10);
- $begin = $deadline < $begin ? $deadline : $begin;
- $deadlineList[$taskID] = $deadline;
- }
- $workingDays = $this->loadModel('holiday')->getActualWorkingDays($begin, $end);
- $storyVersionPairs = $this->loadModel('task')->getTeamStoryVersion(array_keys($tasks));
- foreach($tasks as $taskID => $task)
- {
- /* Story changed or not. */
- $task->storyVersion = zget($storyVersionPairs, $task->id, $task->storyVersion);
- $task->needConfirm = false;
- if(!empty($task->storyStatus) && $task->storyStatus == 'active' && !in_array($task->status, array('cancel', 'closed')) && $task->latestStoryVersion > $task->storyVersion)
- {
- $task->needConfirm = true;
- $task->status = 'changed';
- }
- /* Delayed or not?. */
- $isNotCancel = !in_array($task->status, array('cancel', 'closed')) || ($task->status == 'closed' && !helper::isZeroDate($task->finishedDate) && $task->closedReason != 'cancel');
- $isComputeDelay = $isNotCancel && !empty($deadlineList[$taskID]);
- if($isComputeDelay) $task = $this->task->computeDelay($task, $deadlineList[$taskID], $workingDays);
- }
- return $tasks;
- }
- /**
- * 根据阶段的开始和结束,计算工作日。
- * Calc stage days by stage begin and end.
- *
- * @param string $start
- * @param string $end
- * @access public
- * @return int
- */
- public function calcDaysForStage($start, $end)
- {
- $weekend = $this->config->execution->weekend;
- $days = range(strtotime($start), strtotime($end), 86400);
- foreach($days as $key => $day)
- {
- $weekDay = date('N', $day);
- if(($weekend == 2 && $weekDay == 6) || $weekDay == 7) unset($days[$key]);
- }
- return count($days);
- }
- }
|