| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035 |
- <?php
- /**
- * The tao 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 lanzongjun <lanzongjun@easycorp.ltd>
- * @link https://www.zentao.net
- */
- class programplanTao extends programplanModel
- {
- /**
- * 获取阶段信息。
- * Get stage list.
- *
- * @param int $executionID
- * @param int $productID
- * @param string $browseType
- * @param string $orderBy
- * @access protected
- * @return array
- */
- protected function getStageList($executionID, $productID, $browseType, $orderBy = 'id_asc')
- {
- if(empty($executionID)) return array();
- $projectModel = $this->dao->select('model')->from(TABLE_PROJECT)->where('id')->eq($executionID)->fetch('model');
- $stageList = $this->dao->select('t1.*')->from(TABLE_EXECUTION)->alias('t1')
- ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.id = t2.project')
- ->where('1 = 1')
- ->beginIF(!in_array($projectModel, array('waterfallplus', 'ipd')))->andWhere('t1.type')->eq('stage')->fi()
- ->beginIF($productID)->andWhere('t2.product')->eq($productID)->fi()
- ->beginIF($browseType == 'all' || $browseType == 'leaf')->andWhere('t1.project')->eq($executionID)->fi()
- ->beginIF($browseType == 'parent')->andWhere('t1.parent')->eq($executionID)->fi()
- ->beginIF(!$this->app->user->admin)->andWhere('t1.id')->in($this->app->user->view->sprints)->fi()
- ->andWhere('((t1.enabled')->eq('on')
- ->andWhere('t1.deleted')->eq('0')
- ->markRight(1)
- ->beginIF($this->app->rawModule == 'programplan' && $this->app->rawMethod == 'create')
- ->orWhere('(t1.enabled')->eq('off')
- ->andWhere('t1.deleted')->eq('1')
- ->markRight(1)
- ->fi()
- ->markRight(1)
- ->orderBy($orderBy)
- ->fetchAll('id', false);
- if($projectModel == 'ipd')
- {
- $stagePointGroup = $this->dao->select('execution,category,categoryTitle')->from(TABLE_OBJECT)
- ->where('execution')->in(array_keys($stageList))
- ->andWhere('type')->eq('decision')
- ->andWhere('enabled')->eq('1')
- ->fetchGroup('execution', 'category');
- foreach($stageList as $stage)
- {
- $stage->enabledPoints = array();
- if(isset($stagePointGroup[$stage->id]))
- {
- foreach($stagePointGroup[$stage->id] as $point) $stage->enabledPoints[$point->category] = $point->categoryTitle;
- }
- }
- }
- return $stageList;
- }
- /**
- * 更新项目阶段。
- * update program plan.
- *
- * @param object $plan
- * @param object $oldPlan
- * @access protected
- * @return array|false
- * @param int $planID
- * @param int $projectID
- */
- protected function updateRow($planID, $projectID, $plan)
- {
- if(empty($plan)) return false;
- $oldPlan = $this->fetchByID($planID);
- $planChanged = ($oldPlan->name != $plan->name || $oldPlan->milestone != $plan->milestone || $oldPlan->begin != $plan->begin || $oldPlan->end != $plan->end);
- if($planChanged) $plan->version = $oldPlan->version + 1;
- if(empty($plan->parent)) $plan->parent = $projectID;
- $parentStage = empty($projectID) ? null : $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($plan->parent)->andWhere('type')->eq('stage')->fetch();
- $relatedIdList = $this->loadModel('execution')->getRelatedExecutions($planID);
- $relatedIdList = !empty($relatedIdList) ? implode(',', array_keys($relatedIdList)) : '0';
- $setCode = !empty($this->config->setCode);
- $getName = ($relatedIdList && $oldPlan->project && $parentStage && $oldPlan->parent) ? true : false;
- /* Fix bug #22030. Reset field name for show dao error. */
- $this->lang->project->name = $this->lang->programplan->name;
- $this->lang->project->code = $this->lang->execution->code;
- $this->dao->update(TABLE_PROJECT)->data($plan, 'point,defaultPoint')->autoCheck()
- ->checkIF(!empty($plan->name) && $getName, 'name', 'unique', "id in ({$relatedIdList}) and type in ('sprint','stage') and `project` = {$oldPlan->project} and `deleted` = '0' and `parent` = {$oldPlan->parent}")
- ->checkIF(!empty($plan->code) && $setCode, 'code', 'unique', "id != {$planID} and type in ('sprint','stage','kanban') and `project` = {$oldPlan->project} and `deleted` = '0' and `parent` = {$oldPlan->parent}")
- ->where('id')->eq($planID)
- ->exec();
- /* IPD项目停/启用阶段时,子阶段同样停/启用。*/
- if(isset($plan->enabled) && $oldPlan->enabled != $plan->enabled)
- {
- $this->dao->update(TABLE_PROJECT)
- ->set('deleted')->eq($plan->deleted)
- ->set('enabled')->eq($plan->enabled)
- ->where('project')->eq($projectID)
- ->andWhere('grade')->gt(1)
- ->andWhere("FIND_IN_SET($planID, `path`)")
- ->exec();
- }
- if($planChanged) $this->insertProjectSpec($planID, $plan);
- if(dao::isError()) return false;
- return common::createChanges($oldPlan, $plan);
- }
- /**
- * 获取父阶段键值对。
- * Get parent stage list.
- *
- * @param int $executionID
- * @param int $planID
- * @param int $productID
- * @param string $param
- * @access protected
- * @return array|false
- */
- protected function getParentStages($executionID, $planID, $productID, $param = '')
- {
- dao::$filterTpl = 'skip';
- $parentStage = $this->dao->select('t1.id, t1.name')->from(TABLE_PROJECT)->alias('t1')
- ->beginIF($productID)
- ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.id = t2.project')
- ->fi()
- ->where('t1.project')->eq($executionID)
- ->beginIF($productID)
- ->andWhere('t2.product')->eq($productID)
- ->fi()
- ->andWhere('t1.type')->eq('stage')
- ->andWhere('t1.deleted')->eq(0)
- ->andWhere('t1.path')->notlike("%,$planID,%")
- ->beginIF(!$this->app->user->admin)->andWhere('t1.id')->in($this->app->user->view->sprints)->fi()
- ->beginIF(strpos($param, 'noclosed') !== false)->andWhere('status')->ne('closed')->fi()
- ->orderBy('t1.id desc')
- ->fetchPairs();
- if(dao::isError()) return false;
- return $parentStage;
- }
- /**
- * 根据action获取excution。
- * Get new parent and action.
- *
- * @param array $statusCount
- * @param object $parent
- * @param int $startTasks
- * @param string $action
- * @param object $project
- * @access protected
- * @return array
- */
- protected function getNewParentAndAction($statusCount, $parent, $startTasks, $action, $project)
- {
- $count = count($statusCount);
- $newParent = null;
- $parentAction = '';
- $this->loadModel('execution');
- if(isset($statusCount['wait']) && $count == 1 && helper::isZeroDate($parent->realBegan) && $startTasks == 0)
- {
- if($parent->status == 'wait') return array('newParent' => null, 'parentAction' => '');
- $newParent = $this->execution->buildExecutionByStatus('wait');
- $parentAction = 'waitbychild';
- }
- elseif(isset($statusCount['closed']) && $count == 1)
- {
- if($parent->status == 'closed') return array('newParent' => null, 'parentAction' => '');
- $newParent = $this->execution->buildExecutionByStatus('closed');
- $parentAction = 'closedbychild';
- }
- elseif(isset($statusCount['suspended']) && ($count == 1 || (isset($statusCount['closed']) && $count == 2)))
- {
- if($parent->status == 'suspended') return array('newParent' => null, 'parentAction' => '');
- $newParent = $this->execution->buildExecutionByStatus('suspended');
- $parentAction = 'suspendedbychild';
- }
- else
- {
- if($parent->status == 'doing') return array('newParent' => null, 'parentAction' => '');
- $newParent = $this->execution->buildExecutionByStatus('doing');
- $parentAction = $parent->status == 'wait' ? 'startbychildstart' : 'startbychild' . $action;
- }
- return array('newParent' => $newParent, 'parentAction' => $parentAction);
- }
- /**
- * 设置任务基线数据。
- * Set task baseline data.
- *
- * @param array $oldTasks
- * @param array $tasks
- * @access protected
- * @return array
- */
- protected function setTaskBaseline($oldTasks, $tasks)
- {
- foreach($oldTasks as $id => $oldTask)
- {
- if(!isset($tasks[$id])) continue;
- $tasks[$id]->version = $oldTask->version;
- $tasks[$id]->name = $oldTask->name;
- $tasks[$id]->estStarted = $oldTask->estStarted;
- $tasks[$id]->deadline = $oldTask->deadline;
- }
- return $tasks;
- }
- /**
- * 设置甘特图阶段数据。
- * Set the Gantt chart stage data.
- *
- * @param array $plans
- * @param string $browseType
- * @access protected
- * @return array
- */
- protected function initGanttPlans($plans, $browseType = '')
- {
- $this->app->loadLang('stage');
- $datas = $stageIndex = $planIdList = $reviewDeadline = array();
- $today = helper::today();
- $isMilestone = "<icon class='icon icon-flag icon-sm red'></icon> ";
- foreach($plans as $plan)
- {
- $plan->isParent = false;
- if(isset($plans[$plan->parent]))
- {
- $plans[$plan->parent]->isParent = true;
- }
- elseif($browseType == 'bysearch')
- {
- $plans[$plan->id]->parent = 0;
- }
- }
- foreach($plans as $plan)
- {
- $planIdList[$plan->id] = $plan->id;
- $reviewDeadline[$plan->id]['stageEnd'] = $plan->end;
- $reviewDeadline[$plan->id]['stageBegin'] = $plan->begin;
- $data = $this->buildPlanDataForGantt($plan);
- /* Determines if the object is delay. */
- $data->delay = $this->lang->programplan->delayList[0];
- $data->delayDays = 0;
- if($today > $data->endDate and $plan->status != 'closed')
- {
- $data->delay = $this->lang->programplan->delayList[1];
- $data->delayDays = helper::diffDate($today, $data->endDate);
- }
- $datas['data'][$plan->id] = $data;
- $stageIndex[$plan->id] = array('planID' => $plan->id, 'parent' => isset($plans[$plan->parent]) ? $plan->parent : $plan->project, 'totalEstimate' => 0, 'totalConsumed' => 0, 'totalReal' => 0, 'path' => $plan->path);
- }
- return array('datas' => $datas, 'stageIndex' => $stageIndex, 'planIdList' => $planIdList, 'reviewDeadline' => $reviewDeadline);
- }
- /**
- * 设置甘特图任务数据。
- * Set the Gantt chart task data.
- *
- * @param array $tasks
- * @param array $plans
- * @param string $selectCustom
- * @param array $datas
- * @param array $stageIndex
- * @access protected
- * @return array
- */
- protected function setTask($tasks, $plans, $selectCustom, $datas, $stageIndex)
- {
- $this->app->loadLang('task');
- $this->loadModel('holiday');
- $executions = array();
- $today = helper::today();
- $taskTeams = $this->dao->select('task,account')->from(TABLE_TASKTEAM)->where('task')->in(array_keys($tasks))->fetchGroup('task', 'account');
- $users = $this->loadModel('user')->getPairs('noletter');
- $firstTask = reset($tasks);
- $projectID = $firstTask ? $firstTask->project : 0;
- $taskDateLimit = $this->dao->select('taskDateLimit')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch('taskDateLimit');
- foreach($tasks as $task)
- {
- $plan = zget($plans, $task->execution, null);
- $dateLimit = $this->getTaskDateLimit($task, $plan, $taskDateLimit == 'limit' ? zget($tasks, $task->parent, null) : null);
- $data = $this->buildTaskDataForGantt($task, $dateLimit);
- $data->id = $task->execution . '-' . $task->id;
- $data->parent = $task->parent > 0 && isset($tasks[$task->parent]) ? $task->execution . '-' . $task->parent : $task->execution;
- $data->allowLinks = $plan->type == 'kanban' ? false : true;
- if(!isset($executions[$task->execution])) $executions[$task->execution] = $this->dao->select('status')->from(TABLE_EXECUTION)->where('id')->eq($task->execution)->fetch('status');
- /* Determines if the object is delay. */
- $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;
- }
- /* If multi task then show the teams. */
- if($task->mode == 'multi' && !empty($taskTeams[$task->id])) $data->owner_id = implode(',', array_map(function($assignedTo) use($users){return zget($users, $assignedTo);}, array_keys($taskTeams[$task->id])));
- if(strpos($selectCustom, 'task') !== false) $datas['data'][$data->id] = $data;
- if($task->isParent) continue;
- foreach($stageIndex as $index => $stage)
- {
- if($stage['planID'] != $task->execution) continue;
- if(!isset($stageIndex[$index])) continue;
- $stageIndex[$index]['totalEstimate'] += $task->estimate;
- $stageIndex[$index]['totalConsumed'] += $task->consumed;
- $stageIndex[$index]['totalReal'] += ((($task->status == 'closed' || $task->status == 'cancel') ? 0 : $task->left) + $task->consumed);
- foreach(explode(',', $stage['path']) as $planID)
- {
- if(!isset($stageIndex[$planID]) || $planID == $stage['planID']) continue;
- $stageIndex[$planID]['totalEstimate'] += $task->estimate;
- $stageIndex[$planID]['totalConsumed'] += $task->consumed;
- $stageIndex[$planID]['totalReal'] += ((($task->status == 'closed' || $task->status == 'cancel') ? 0 : $task->left) + $task->consumed);
- }
- }
- }
- return array('datas' => $datas, 'stageIndex' => $stageIndex);
- }
- /**
- * 获取子阶段数量。
- * Get stage count.
- *
- * @param int $planID
- * @param string $mode
- * @access protected
- * @return int
- */
- protected function getStageCount($planID, $mode = '')
- {
- return $this->dao->select('COUNT(1) AS count')->from(TABLE_PROJECT)
- ->where('parent')->eq($planID)
- ->andWhere('type')->eq('stage')
- ->beginIF($mode == 'milestone')->andWhere('milestone')->eq(1)->fi()
- ->andWhere('deleted')->eq(0)
- ->fetch('count');
- }
- /**
- * 计算阶段在数据库中的位置序号。
- * Compute the new order for programplan. if orders is not set then begin with the order in zt_project table.
- *
- * @param array $orders
- * @param array $plans
- * @access protected
- * @return array
- */
- protected function computeOrders($orders, $plans)
- {
- if(empty($orders)) $orders = array();
- asort($orders);
- $planCount = count($plans);
- if(count($orders) < $planCount)
- {
- $orderIndex = empty($orders) ? 0 : count($orders);
- $lastID = $this->dao->select('id')->from(TABLE_EXECUTION)->orderBy('id_desc')->limit(1)->fetch('id');
- for($i = $orderIndex; $i < $planCount; $i ++)
- {
- $lastID ++;
- $orders[$i] = $lastID * 5;
- }
- }
- return $orders;
- }
- /**
- * 插入项目细节。
- * Insert project spec.
- * @param int $planID
- * @param object $plan
- * @access protected
- * @return bool
- */
- protected function insertProjectSpec($planID, $plan)
- {
- if(empty($planID)) return false;
- $spec = new stdclass();
- $spec->project = $planID;
- $spec->version = $plan->version;
- $spec->name = $plan->name;
- $spec->milestone = $plan->milestone;
- $spec->begin = $plan->begin;
- $spec->end = $plan->end;
- $this->dao->insert(TABLE_PROJECTSPEC)->data($spec)->exec();
- return !dao::isError();
- }
- /**
- * 获取阶段百分比。
- * Get total percent.
- *
- * @param object $stage
- * @param bool $parent
- * @access public
- * @return int|float
- */
- protected function getTotalPercent($stage, $parent = false)
- {
- /* When parent is equal to true, query the total workload of the subphase. */
- $executionID = $parent ? $stage->id : $stage->project;
- $plans = $this->getStageList((int)$executionID, (int)$stage->product, 'parent');
- $totalPercent = 0;
- $stageID = $stage->id;
- foreach($plans as $id => $stage)
- {
- if($id == $stageID) continue;
- $totalPercent += $stage->percent;
- }
- return $totalPercent;
- }
- /**
- * Get point end date.
- *
- * @param int $planID
- * @param object $point
- * @param array $reviewDeadline
- * @access protected
- * @return string
- */
- protected function getPointEndDate($planID, $point, $reviewDeadline)
- {
- if($point->end and !helper::isZeroDate($point->end)) return $point->end;
- $end = $reviewDeadline[$planID]['stageEnd'];
- $begin = $reviewDeadline[$planID]['stageBegin'];
- $pointType = $this->dao->select('type')->from(TABLE_DECISION)->where('id')->eq($point->id)->fetch('type');
- if($pointType == 'DCP') $end = $this->getReviewDeadline($end, 2);
- if($pointType == 'TR')
- {
- if(isset($reviewDeadline[$planID]['taskEnd']) and !helper::isZeroDate($reviewDeadline[$planID]['taskEnd'])) return $reviewDeadline[$planID]['taskEnd'];
- $end = $this->getReviewDeadline($end);
- }
- return $end <= $begin ? $begin : $end;
- }
- /**
- * 构建IPD版本的甘特图数据。
- * Build gantt's data for ipd edition.
- *
- * @param array $datas
- * @param int $projectID
- * @param int $productID
- * @param string $selectCustom
- * @param array $reviewDeadline
- * @access protected
- * @return array
- */
- protected function buildGanttData4IPD($datas, $projectID, $productID, $selectCustom, $reviewDeadline)
- {
- if($this->config->edition != 'ipd') return $datas;
- $this->loadModel('review');
- $reviewPoints = $this->dao->select('t1.*, t2.status, t2.lastReviewedDate,t2.id as reviewID')->from(TABLE_OBJECT)->alias('t1')
- ->leftJoin(TABLE_REVIEW)->alias('t2')->on('t1.id = t2.object')
- ->where('t1.deleted')->eq('0')
- ->andWhere('t1.project')->eq($projectID)
- ->andWhere('t1.enabled')->eq(1)
- ->fetchAll('id', false);
- foreach($datas['data'] as $plan)
- {
- if($plan->type != 'plan') continue;
- foreach($reviewPoints as $point)
- {
- if($point->execution != $plan->id) continue;
- if(!isset($point->status)) $point->status = '';
- $dataID = "{$plan->id}-point{$point->category}-{$point->id}";
- if($selectCustom && strpos($selectCustom, "point") !== false && !$plan->parent) $datas['data'][$dataID] = $this->buildPointDataForGantt($plan->id, $point, $reviewDeadline);
- }
- }
- return $datas;
- }
- /**
- * 获取创建时的关联产品。
- * Get linkProducts for create.
- *
- * @param int $projectID
- * @param int $productID
- * @access protected
- * @return array
- */
- protected function getLinkProductsForCreate($projectID, $productID)
- {
- $project = $this->fetchByID($projectID, 'project');
- $linkProducts = array();
- $linkBranches = array();
- $productList = $this->loadModel('product')->getProducts($projectID);
- if($project && $project->stageBy == 'product')
- {
- $linkProducts = array(0 => $productID);
- if(!empty($productList[$productID])) $linkBranches = array(0 => zget($productList[$productID], 'branches', array()));
- }
- else
- {
- $linkProducts = array_keys($productList);
- foreach($linkProducts as $index => $productID)
- {
- if(!empty($productList)) $linkBranches[$index] = $productList[$productID]->branches;
- }
- }
- return array('products' => $linkProducts, 'branch' => $linkBranches);
- }
- /**
- * 插入阶段数据。
- * Insert stage.
- * @param object $plan
- * @param int $projectID
- * @param int $productID
- * @param int $parentID
- * @access protected
- * @return int|false
- */
- protected function insertStage($plan, $projectID, $productID, $parentID)
- {
- $this->loadModel('execution');
- $project = $this->fetchById($projectID, 'project');
- $account = $this->app->user->account;
- unset($plan->id);
- $plan->status = 'wait';
- $plan->stageBy = empty($project) ? 'product' : $project->stageBy;
- $plan->version = 1;
- $plan->parentVersion = $plan->parent == 0 ? 0 : $this->dao->findByID($plan->parent)->from(TABLE_PROJECT)->fetch('version');
- $plan->team = mb_substr($plan->name, 0, 30);
- $plan->openedBy = $account;
- $plan->openedDate = helper::now();
- $plan->openedVersion = $this->config->version;
- if(!isset($plan->acl)) $plan->acl = $this->dao->findByID($plan->parent)->from(TABLE_PROJECT)->fetch('acl');
- $this->dao->insert(TABLE_PROJECT)->data($plan, 'point,defaultPoint')->autoCheck()->exec();
- if(dao::isError()) return false;
- $stageID = (int)$this->dao->lastInsertID();
- $this->insertProjectSpec($stageID, $plan);
- /* Ipd project create default review points. */
- if($project && $project->model == 'ipd' && $this->config->edition == 'ipd' && !$parentID) $this->loadModel('review')->createDefaultPoint($project->id, $stageID, $plan->defaultPoint);
- if($plan->type == 'kanban')
- {
- $execution = $this->execution->getByID($stageID);
- $this->loadModel('kanban')->createRDKanban($execution);
- }
- $this->loadModel('execution');
- if($projectID) $this->execution->createMainLib($projectID, $stageID);
- $this->execution->addExecutionMembers($stageID, array($account, $plan->PM));
- $this->setTreePath($stageID);
- $this->computeProgress($stageID, 'create');
- /* Add the execution team members to parent executions. */
- $stagePath = $this->dao->findByID($stageID)->from(TABLE_PROJECT)->fetch('path');
- foreach(explode(',', $stagePath) as $stageParentID)
- {
- if(empty($stageParentID) || $stageParentID == $projectID) continue;
- $this->execution->addExecutionMembers((int)$stageParentID, array($plan->PM));
- }
- return $stageID;
- }
- /**
- * 根据任务列表,构建任务分组。
- * Build task group by assignedTo.
- *
- * @param array $task
- * @access protected
- * @return array
- * @param mixed[] $tasks
- */
- protected function buildTaskGroup($tasks)
- {
- $taskGroup = array();
- $multiTasks = array();
- foreach($tasks as $taskID => $task)
- {
- $taskGroup[$task->assignedTo][$taskID] = $task;
- if($task->mode == 'multi') $multiTasks[$taskID] = $task->assignedTo;
- }
- if(empty($multiTasks)) return $taskGroup;
- $taskTeams = $this->dao->select('t1.*,t2.realname')->from(TABLE_TASKTEAM)->alias('t1')
- ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account = t2.account')
- ->where('t1.task')->in(array_keys($multiTasks))
- ->orderBy('t1.order')
- ->fetchGroup('task', 'id');
- foreach($taskTeams as $taskID => $team)
- {
- $assignedTo = $multiTasks[$taskID];
- foreach($team as $member)
- {
- $account = $member->account;
- if($account == $assignedTo) continue;
- if(!isset($taskGroup[$account])) $taskGroup[$account] = array();
- $taskGroup[$account][$taskID] = clone $tasks[$taskID];
- $taskGroup[$account][$taskID]->id = $taskID . '_' . $account;
- $taskGroup[$account][$taskID]->realID = $taskID;
- $taskGroup[$account][$taskID]->assignedTo = $account;
- $taskGroup[$account][$taskID]->realname = $member->realname;
- }
- }
- return $taskGroup;
- }
- /**
- * Build plan data for gantt.
- *
- * @param object $plan
- * @access protected
- * @return object
- */
- protected function buildPlanDataForGantt($plan)
- {
- $start = helper::isZeroDate($plan->begin) ? '' : $plan->begin;
- $end = helper::isZeroDate($plan->end) ? '' : $plan->end;
- $realBegan = helper::isZeroDate($plan->realBegan) ? '' : $plan->realBegan;
- $realEnd = helper::isZeroDate($plan->realEnd) ? '' : $plan->realEnd;
- $isMilestone = "<icon class='icon icon-flag icon-sm red'></icon> ";
- $data = new stdclass();
- $data->id = $plan->id;
- $data->type = 'plan';
- $data->executionType = $plan->type;
- $data->text = empty($plan->milestone) ? $plan->name : $plan->name . $isMilestone;
- $data->name = $plan->name;
- $data->attribute = zget($this->lang->stage->typeList, $plan->attribute);
- $data->milestone = zget($this->lang->programplan->milestoneList, $plan->milestone);
- $data->milestonecode = $plan->milestone;
- $data->owner_id = $plan->PM;
- $data->status = $this->processStatus('execution', $plan);
- $data->begin = $start;
- $data->deadline = $end;
- $data->realBegan = $realBegan ? substr($realBegan, 0, 10) : '';
- $data->realEnd = $realEnd ? substr($realEnd, 0, 10) : '';
- $data->parent = $plan->grade == 1 ? 0 :$plan->parent;
- $data->isParent = $plan->isParent;
- $data->isTpl = $plan->isTpl;
- $data->open = true;
- $data->start_date = $start;
- $data->endDate = $end;
- $data->duration = 1;
- $data->color = $this->lang->execution->gantt->stage->color;
- $data->progressColor = $this->lang->execution->gantt->stage->progressColor;
- $data->textColor = $this->lang->execution->gantt->stage->textColor;
- $data->bar_height = $this->lang->execution->gantt->bar_height;
- /* Set default progress from database. */
- $data->progress = $plan->progress / 100;
- $data->taskProgress = $plan->progress . '%';
- $data->frozen = $plan->frozen;
- if($data->endDate > $data->start_date) $data->duration = helper::diffDate($data->endDate, $data->start_date) + 1;
- if(empty($data->start_date) || empty($data->endDate)) $data->duration = 1;
- if(!empty($this->config->setPercent)) $data->percent = $plan->percent;
- if($data->start_date) $data->start_date = date('d-m-Y', strtotime($data->start_date));
- return $data;
- }
- /**
- * Guild point data for gantt.
- *
- * @param int $planID
- * @param object $point
- * @param array $reviewDeadline
- * @access protected
- * @return object
- */
- protected function buildPointDataForGantt($planID, $point, $reviewDeadline)
- {
- $statusList = array();
- if(isset($this->lang->review->statusList)) $statusList = $this->lang->review->statusList;
- $end = $this->getPointEndDate($planID, $point, $reviewDeadline);
- $data = new stdclass();
- $data->id = $planID . '-point' . $point->category . '-' . $point->id;
- $data->reviewID = $point->reviewID;
- $data->type = 'point';
- $data->text = "<i class='icon-seal'></i> " . $point->title;
- $data->name = $point->title;
- $data->attribute = '';
- $data->milestone = '';
- $data->owner_id = '';
- $data->rawStatus = $point->status;
- $data->status = $point->status ? zget($statusList, $point->status) : $this->lang->programplan->wait;
- $data->status = "<span class='status-{$point->status}'>" . $data->status . '</span>';
- $data->begin = $end;
- $data->deadline = $end;
- $data->realBegan = $point->createdDate;
- $data->realEnd = $point->lastReviewedDate;;
- $data->parent = $planID;
- $data->open = true;
- $data->start_date = $end;
- $data->endDate = $end;
- $data->duration = 1;
- $data->color = isset($this->lang->programplan->reviewColorList[$point->status]) ? $this->lang->programplan->reviewColorList[$point->status] : '#FC913F';
- $data->progressColor = $this->lang->execution->gantt->stage->progressColor;
- $data->textColor = $this->lang->execution->gantt->stage->textColor;
- $data->bar_height = $this->lang->execution->gantt->bar_height;
- if($data->start_date) $data->start_date = date('d-m-Y', strtotime($data->start_date));
- return $data;
- }
- /**
- * 构建甘特图的分组数据。
- * Build group data for gantt.
- *
- * @param int $groupID
- * @param string $group
- * @param array $users
- * @access protected
- * @return object
- */
- protected function buildGroupDataForGantt($groupID, $group, $users)
- {
- $this->app->loadLang('task');
- $groupName = $group;
- $groupName = $group != '/' ? zget($users, $group) : $this->lang->task->noAssigned;
- $dataGroup = new stdclass();
- $dataGroup->id = $groupID;
- $dataGroup->type = 'group';
- $dataGroup->text = $groupName;
- $dataGroup->percent = '';
- $dataGroup->attribute = '';
- $dataGroup->milestone = '';
- $dataGroup->owner_id = $group;
- $dataGroup->status = '';
- $dataGroup->begin = '';
- $dataGroup->deadline = '';
- $dataGroup->realBegan = '';
- $dataGroup->realEnd = '';
- $dataGroup->parent = 0;
- $dataGroup->open = true;
- $dataGroup->progress = '';
- $dataGroup->taskProgress = '';
- $dataGroup->color = $this->lang->execution->gantt->stage->color;
- $dataGroup->progressColor = $this->lang->execution->gantt->stage->progressColor;
- $dataGroup->textColor = $this->lang->execution->gantt->stage->textColor;
- $dataGroup->bar_height = $this->lang->execution->gantt->bar_height;
- return $dataGroup;
- }
- /**
- * 构建甘特图的任务数据。
- * Build task data for gantt.
- *
- * @param int $groupID
- * @param object $task
- * @param array $dateLimit array('start' => $start, 'end' => $end, 'realBegan' => $realBegan, 'realEnd' => $realEnd);
- * @param array $tasksMap
- * @access protected
- * @return object
- */
- protected function buildTaskDataForGantt($task, $dateLimit, $groupID = 0, $tasksMap = array())
- {
- $taskPri = "<span class='pri-%s align-middle' title='%s'>%s</span> ";
- $pri = zget($this->lang->task->priList, $task->pri);
- $priIcon = sprintf($taskPri, $task->pri, $pri, $pri);
- $total = $task->left + $task->consumed;
- $progress = $total > 0 ? round($task->consumed / $total, 3) : 0;
- $data = new stdclass();
- $data->id = $task->id;
- $data->type = 'task';
- $data->text = $priIcon . "<span class='gantt_title'>{$task->name}</span>";
- $data->percent = '';
- $data->status = $task->status == 'changed' ? $this->lang->task->storyChange : $this->processStatus('task', $task);
- $data->owner_id = $task->assignedTo;
- $data->attribute = '';
- $data->milestone = '';
- $data->begin = substr($dateLimit['start'], 0, 10);
- $data->deadline = substr($dateLimit['end'], 0, 10);
- $data->realBegan = $dateLimit['realBegan'] ? substr($dateLimit['realBegan'], 0, 10) : '';
- $data->realEnd = $dateLimit['realEnd'] ? substr($dateLimit['realEnd'], 0, 10) : '';
- $data->pri = $task->pri;
- $data->parent = ($task->parent > 0 and $task->assignedTo != '' and !empty($tasksMap[$task->parent]->assignedTo)) ? $task->parent : $groupID;
- $data->open = true;
- $data->progress = $progress;
- $data->taskProgress = ($progress * 100) . '%';
- $data->start_date = $dateLimit['start'];
- $data->endDate = $dateLimit['end'];
- $data->duration = 1;
- $data->estimate = $task->estimate;
- $data->consumed = $task->consumed;
- $data->color = zget($this->lang->execution->gantt->color, $task->pri, $this->lang->execution->gantt->defaultColor);
- $data->progressColor = zget($this->lang->execution->gantt->progressColor, $task->pri, $this->lang->execution->gantt->defaultProgressColor);
- $data->textColor = zget($this->lang->execution->gantt->textColor, $task->pri, $this->lang->execution->gantt->defaultTextColor);
- $data->bar_height = $this->lang->execution->gantt->bar_height;
- if($data->endDate > $data->start_date) $data->duration = helper::diffDate($data->endDate, $data->start_date) + 1;
- if(empty($data->start_date) or empty($data->endDate)) $data->duration = 1;
- if($data->start_date) $data->start_date = date('d-m-Y', strtotime($data->start_date));
- return $data;
- }
- /**
- * 构建甘特图的关系链接
- * Build gantt links.
- *
- * @param int $projectID
- * @param array $tasks
- * @access protected
- * @return array
- */
- protected function buildGanttLinks($projectID, $tasks = array())
- {
- $this->app->loadConfig('execution');
- if($this->config->edition == 'open') return array();
- $links = array();
- $relations = $this->dao->select('*')->from(TABLE_RELATIONOFTASKS)->where('project')->eq($projectID)->orderBy('task,pretask')->fetchAll();
- foreach($relations as $relation)
- {
- $link = array();
- $link['id'] = $relation->id;
- $link['source'] = !empty($tasks[$relation->pretask]->id) ? $tasks[$relation->pretask]->id : $relation->pretask;
- $link['target'] = !empty($tasks[$relation->task]->id) ? $tasks[$relation->task]->id : $relation->task;
- $link['type'] = $this->config->execution->gantt->linkType[$relation->condition][$relation->action];
- $links[] = $link;
- }
- return $links;
- }
- /**
- * 设置阶段统计数据。
- * Set stage summary.
- *
- * @param array $ganttData
- * @param array $stages
- * @access protected
- * @return array
- */
- protected function setStageSummary($ganttData, $stages)
- {
- foreach($stages as $index => $stage)
- {
- if(!isset($ganttData['data'][$index])) continue;
- $progress = empty($stage['totalReal']) ? 0 : round($stage['totalConsumed'] / $stage['totalReal'], 3);
- $ganttData['data'][$index]->progress = $progress;
- $ganttData['data'][$index]->taskProgress = ($progress * 100) . '%';
- $ganttData['data'][$index]->estimate = $stage['totalEstimate'];
- $ganttData['data'][$index]->consumed = $stage['totalConsumed'];
- }
- return $ganttData;
- }
- /**
- * 获取任务任务限制。获取该任务在甘特图中的开始,结束时间。
- * Get task date limit.
- *
- * @param object $task
- * @param object|null $execution
- * @param object|null $parent
- * @access protected
- * @return array
- */
- protected function getTaskDateLimit($task, $execution = null, $parent = null)
- {
- $estStart = helper::isZeroDate($task->estStarted) ? '' : $task->estStarted;
- $estEnd = helper::isZeroDate($task->deadline) ? '' : $task->deadline;
- $realBegan = helper::isZeroDate($task->realStarted) ? '' : $task->realStarted;
- $realEnd = (in_array($task->status, array('done', 'closed')) and !helper::isZeroDate($task->finishedDate)) ? $task->finishedDate : '';
- if($parent)
- {
- if(empty($estStart) && !helper::isZeroDate($parent->estStarted)) $estStart = $parent->estStarted;
- if(empty($estEnd) && !helper::isZeroDate($parent->deadline)) $estEnd = $parent->deadline;
- }
- $start = $estStart;
- $end = $estEnd;
- if(empty($start) and $execution) $start = $execution->begin;
- if(empty($end) and $execution) $end = $execution->end;
- if($start > $end) $end = $start;
- return array('start' => $start, 'end' => $end, 'realBegan' => $realBegan, 'realEnd' => $realEnd);
- }
- /**
- * Get five days ago.
- *
- * @param string $date
- * @param int $date
- * @access public
- * @return string
- * @param int $counter
- */
- public function getReviewDeadline($date, $counter = 5)
- {
- if(helper::isZeroDate($date)) return '';
- $this->loadModel('holiday');
- $weekendDays = array(6, 7);
- $timestamp = strtotime($date);
- $index = 0;
- while($index < $counter)
- {
- $timestamp -= 24 * 3600;
- $weekday = date('N', $timestamp);
- $currentDate = date('Y-m-d', $timestamp);
- if(!in_array($weekday, $weekendDays) and !$this->holiday->isHoliday($currentDate)) $index ++;
- }
- return date('Y-m-d', $timestamp);
- }
- /**
- * 获取基线数据。
- * Get baseline data.
- *
- * @param array $oldPlans
- * @param array $plans
- * @access protected
- * @return array
- */
- protected function setPlanBaseline($oldPlans, $plans)
- {
- foreach($oldPlans as $id => $oldPlan)
- {
- if(!isset($plans[$id])) continue;
- $plans[$id]->version = $oldPlan->version;
- $plans[$id]->name = $oldPlan->name;
- $plans[$id]->milestone = $oldPlan->milestone;
- $plans[$id]->begin = $oldPlan->begin;
- $plans[$id]->end = $oldPlan->end;
- }
- return $plans;
- }
- /**
- * 将父阶段数据转移到第一个子阶段中。
- * Sync parent data to first stage.
- *
- * @param int $executionID
- * @param int $parentID
- * @access protected
- * @return bool
- */
- protected function syncParentData($executionID, $parentID)
- {
- if(empty($executionID) || empty($parentID)) return false;
- $this->dao->update(TABLE_TASK)->set('execution')->eq($executionID)->where('execution')->eq($parentID)->andWhere('deleted')->eq('0')->exec();
- $this->dao->update(TABLE_EFFORT)->set('execution')->eq($executionID)->where('execution')->eq($parentID)->andWhere('deleted')->eq('0')->exec();
- $this->dao->update(TABLE_BUG)->set('execution')->eq($executionID)->where('execution')->eq($parentID)->andWhere('deleted')->eq('0')->exec();
- $this->dao->update(TABLE_CASE)->set('execution')->eq($executionID)->where('execution')->eq($parentID)->andWhere('deleted')->eq('0')->exec();
- $this->dao->update(TABLE_BUILD)->set('execution')->eq($executionID)->where('execution')->eq($parentID)->andWhere('deleted')->eq('0')->exec();
- $this->dao->update(TABLE_TESTTASK)->set('execution')->eq($executionID)->where('execution')->eq($parentID)->andWhere('deleted')->eq('0')->exec();
- $this->dao->update(TABLE_TESTREPORT)->set('execution')->eq($executionID)->where('execution')->eq($parentID)->andWhere('deleted')->eq('0')->exec();
- $this->dao->update(TABLE_ACTION)->set('execution')->eq($executionID)->where('execution')->eq($parentID)->andWhere('objectType')->ne('execution')->exec();
- $this->dao->update(TABLE_ACTIONRECENT)->set('execution')->eq($executionID)->where('execution')->eq($parentID)->andWhere('objectType')->ne('execution')->exec();
- $this->dao->update(TABLE_DOCLIB)->set('execution')->eq($executionID)->where('type')->eq('execution')->andWhere('execution')->eq($parentID)->andWhere('main')->eq('0')->andWhere('deleted')->eq('0')->exec();
- $storyIdSQL = $this->dao->select('id')->from(TABLE_STORY)->where('deleted')->eq('0')->get();
- $this->dao->update(TABLE_PROJECTSTORY)->set('project')->eq($executionID)->where('project')->eq($parentID)->andWhere('story')->subIn($storyIdSQL)->exec();
- $caseIdSQL = $this->dao->select('id')->from(TABLE_CASE)->where('deleted')->eq('0')->get();
- $this->dao->update(TABLE_PROJECTCASE)->set('project')->eq($executionID)->where('project')->eq($parentID)->andWhere('case')->subIn($caseIdSQL)->exec();
- /* Update doc in the main doc lib. */
- $parentLibID = $this->dao->select('id')->from(TABLE_DOCLIB)->where('type')->eq('execution')->andWhere('execution')->eq($parentID)->andWhere('main')->eq('1')->limit(1)->fetch('id');
- $libID = $this->dao->select('id')->from(TABLE_DOCLIB)->where('type')->eq('execution')->andWhere('execution')->eq($executionID)->andWhere('main')->eq('1')->limit(1)->fetch('id');
- if($parentLibID && $libID) $this->dao->update(TABLE_DOC)->set('execution')->eq($executionID)->set('lib')->eq($libID)->where('lib')->eq($parentLibID)->andWhere('deleted')->eq('0')->exec();
- /* Update task and doc module. */
- $this->dao->update(TABLE_MODULE)->set('root')->eq($executionID)->where('root')->eq($parentID)->andWhere('type')->eq('task')->andWhere('deleted')->eq('0')->exec();
- $this->dao->update(TABLE_MODULE)->set('root')->eq($libID)->where('root')->eq($parentLibID)->andWhere('type')->eq('doc')->andWhere('deleted')->eq('0')->exec();
- /* Update stage info. */
- $parent = $this->fetchByID($parentID);
- $data = new stdclass();
- $data->status = $parent->status;
- $data->progress = $parent->progress;
- $data->estimate = $parent->estimate;
- $data->left = $parent->left;
- $data->consumed = $parent->consumed;
- $this->dao->update(TABLE_EXECUTION)->data($data)->where('id')->eq($executionID)->exec();
- return !dao::isError();
- }
- }
|