| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882 |
- <?php
- /**
- * The tao file of execution module of ZenTaoPMS.
- *
- * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
- * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
- * @author Shujie Tian <tianshujie@easysoft.ltd>
- * @package execution
- * @link https://www.zentao.net
- */
- class executionTao extends executionModel
- {
- /**
- * 根据给定条件构建执行键值对。
- * Build execution id:name pairs through the conditions.
- *
- * @param string $mode all|noclosed|stagefilter|withdelete|multiple|leaf|order_asc|noprefix|withobject|hideMultiple
- * @param array $allExecutions
- * @param array $executions
- * @param array $parents
- * @param array $projectPairs
- * @param string $projectModel
- * @access protected
- * @return array
- */
- protected function buildExecutionPairs($mode = '', $allExecutions = array(), $executions = array(), $parents = array(), $projectPairs = array(), $projectModel = '')
- {
- $executionPairs = array();
- $noMultiples = array();
- foreach($executions as $execution)
- {
- if(strpos($mode, 'leaf') !== false && isset($parents[$execution->id])) continue; // Only show leaf.
- if(strpos($mode, 'noclosed') !== false && ($execution->status == 'done' or $execution->status == 'closed')) continue;
- if(strpos($mode, 'stagefilter') !== false && isset($projectModel) && in_array($projectModel, array('waterfall', 'waterfallplus')) && in_array($execution->attribute, array('request', 'design', 'review'))) continue; // Some stages of waterfall && waterfallplus not need.
- if(empty($execution->multiple)) $noMultiples[$execution->id] = $execution->project;
- /* Set execution name. */
- $paths = array_slice(explode(',', trim($execution->path, ',')), 1);
- $executionName = '';
- foreach($paths as $path)
- {
- if(isset($allExecutions[$path])) $executionName .= '/' . $allExecutions[$path]->name;
- }
- if(strpos($mode, 'withobject') !== false) $executionName = zget($projectPairs, $execution->project, '') . $executionName;
- if(strpos($mode, 'noprefix') !== false) $executionName = ltrim($executionName, '/');
- $executionPairs[$execution->id] = $executionName;
- }
- if($noMultiples)
- {
- if(strpos($mode, 'hideMultiple') !== false)
- {
- foreach($noMultiples as $executionID => $projectID) $executionPairs[$executionID] = '';
- }
- else
- {
- $this->app->loadLang('project');
- $noMultipleProjects = $this->dao->select('id, name')->from(TABLE_PROJECT)->where('id')->in($noMultiples)->fetchPairs('id', 'name');
- foreach($noMultiples as $executionID => $projectID)
- {
- if(isset($noMultipleProjects[$projectID])) $executionPairs[$executionID] = $noMultipleProjects[$projectID] . "({$this->lang->project->disableExecution})";
- }
- }
- }
- /* If the executionPairs is empty, to make sure there's an execution in the executionPairs. */
- if(empty($executionPairs) and isset($executions[0]))
- {
- $firstExecution = $executions[0];
- $executionPairs[$firstExecution->id] = $firstExecution->name;
- }
- return $executionPairs;
- }
- /**
- * 构造批量更新执行的数据。
- * Build bathc update execution data.
- *
- * @param object $postData
- * @param array $oldExecutions
- * @access public
- * @return array
- */
- public function buildBatchUpdateExecutions($postData, $oldExecutions)
- {
- $this->loadModel('user');
- $this->loadModel('project');
- $this->app->loadLang('programplan');
- $executions = array();
- $nameList = array();
- $codeList = array();
- $parents = array();
- foreach($oldExecutions as $oldExecution) $parents[$oldExecution->id] = $oldExecution->parent;
- /* Replace required language. */
- if($this->app->tab == 'project')
- {
- $projectModel = $this->dao->select('model')->from(TABLE_PROJECT)->where('id')->eq($this->session->project)->fetch('model');
- if(empty($this->session->project) || $projectModel == 'scrum')
- {
- $this->lang->project->name = $this->lang->execution->name;
- $this->lang->project->code = $this->lang->execution->code;
- }
- else
- {
- $this->lang->project->name = str_replace($this->lang->project->common, $this->lang->project->stage, $this->lang->project->name);
- $this->lang->project->code = str_replace($this->lang->project->common, $this->lang->project->stage, $this->lang->project->code);
- }
- }
- else
- {
- $this->lang->project->name = $this->lang->execution->name;
- $this->lang->project->code = $this->lang->execution->code;
- }
- $this->lang->error->unique = $this->lang->error->repeat;
- $extendFields = $this->getFlowExtendFields();
- foreach($postData->id as $executionID)
- {
- $executionName = $postData->name[$executionID];
- if(isset($postData->code)) $executionCode = $postData->code[$executionID];
- $executionID = (int)$executionID;
- $executions[$executionID] = new stdClass();
- $executions[$executionID]->id = $executionID;
- $executions[$executionID]->name = $executionName;
- $executions[$executionID]->PM = $postData->PM[$executionID];
- $executions[$executionID]->PO = $postData->PO[$executionID];
- $executions[$executionID]->QD = $postData->QD[$executionID];
- $executions[$executionID]->RD = $postData->RD[$executionID];
- $executions[$executionID]->begin = $postData->begin[$executionID];
- $executions[$executionID]->end = $postData->end[$executionID];
- $executions[$executionID]->team = $postData->team[$executionID];
- $executions[$executionID]->desc = htmlspecialchars_decode($postData->desc[$executionID]);
- $executions[$executionID]->days = $postData->days[$executionID];
- $executions[$executionID]->lastEditedBy = $this->app->user->account;
- $executions[$executionID]->lastEditedDate = helper::now();
- if(isset($postData->code)) $executions[$executionID]->code = $executionCode;
- if(isset($postData->project)) $executions[$executionID]->project = zget($postData->project, $executionID, 0);
- if(isset($postData->attribute[$executionID])) $executions[$executionID]->attribute = zget($postData->attribute, $executionID, '');
- if(isset($postData->lifetime[$executionID])) $executions[$executionID]->lifetime = $postData->lifetime[$executionID];
- $oldExecution = $oldExecutions[$executionID];
- $projectID = isset($executions[$executionID]->project) ? (int)$executions[$executionID]->project : (int)$oldExecution->project;
- $project = dao::isError() ? '' : $this->project->getByID($projectID);
- /* Check unique code for edited executions. */
- if(isset($postData->code) && empty($executionCode) && strpos(",{$this->config->execution->edit->requiredFields},", ',code,') !== false)
- {
- dao::$errors["code[$executionID]"] = sprintf($this->lang->error->notempty, $this->lang->execution->execCode);
- }
- elseif(isset($postData->code) and $executionCode)
- {
- if(isset($codeList[$executionCode]))
- {
- dao::$errors["code[$executionID]"] = sprintf($this->lang->error->unique, $this->lang->execution->execCode, $executionCode);
- }
- $codeList[$executionCode] = $executionCode;
- }
- /* Name check. */
- $parentID = $parents[$executionID];
- if(isset($nameList[$executionName]) && !empty($executionName))
- {
- foreach($nameList[$executionName] as $repeatID)
- {
- if($parentID == $parents[$repeatID])
- {
- $type = $oldExecution->type == 'stage' ? 'stage' : 'agileplus';
- $repeatTip = $parentID == $projectID ? $this->lang->programplan->error->sameName : sprintf($this->lang->execution->errorNameRepeat, strtolower(zget($this->lang->programplan->typeList, $type)));
- dao::$errors["name[$executionID]"] = $repeatTip;
- }
- }
- }
- $nameList[$executionName][] = $executionID;
- /* Attribute check. */
- if(isset($postData->attribute) && isset($project->model) && in_array($project->model, array('waterfall', 'waterfallplus')))
- {
- $this->app->loadLang('stage');
- $attribute = isset($executions[$executionID]->attribute) ? $executions[$executionID]->attribute : $oldExecution->attribute;
- if(isset($attributeList[$parentID]))
- {
- $parentAttr = $attributeList[$parentID];
- }
- else
- {
- $parentAttr = dao::isError() ? $attribute : $this->dao->select('attribute')->from(TABLE_PROJECT)->where('id')->eq($parentID)->fetch('attribute');
- }
- if($parentAttr && $parentAttr != $attribute && $parentAttr != 'mix')
- {
- $parentAttr = zget($this->lang->stage->typeList, $parentAttr);
- dao::$errors["attribute[$executionID]"] = sprintf($this->lang->execution->errorAttrMatch, $parentAttr);
- }
- $attributeList[$executionID] = $attribute;
- }
- /* Judge workdays is legitimate. */
- $workdays = helper::diffDate($postData->end[$executionID], $postData->begin[$executionID]) + 1;
- if(isset($postData->days[$executionID]) and $postData->days[$executionID] > $workdays)
- {
- $this->app->loadLang('project');
- dao::$errors["days[{$executionID}]"] = sprintf($this->lang->project->workdaysExceed, $workdays);
- }
- /* Parent stage begin and end check. */
- if(isset($executions[$parentID]))
- {
- $begin = $executions[$executionID]->begin;
- $end = $executions[$executionID]->end;
- $parentBegin = $executions[$parentID]->begin;
- $parentEnd = $executions[$parentID]->end;
- if($begin < $parentBegin)
- {
- dao::$errors["begin[$executionID]"] = sprintf($this->lang->execution->errorLesserParent, $parentBegin);
- }
- if($end > $parentEnd)
- {
- dao::$errors["end[$executionID]"] = sprintf($this->lang->execution->errorGreaterParent, $parentEnd);
- }
- }
- foreach($extendFields as $extendField)
- {
- $executions[$executionID]->{$extendField->field} = $postData->{$extendField->field}[$executionID];
- if(in_array($extendField->type, array('date', 'datetime')) && empty($postData->{$extendField->field}[$executionID]))
- {
- $executions[$executionID]->{$extendField->field} = null;
- continue;
- }
- if(is_array($executions[$executionID]->{$extendField->field})) $executions[$executionID]->{$extendField->field} = implode(',', $executions[$executionID]->{$extendField->field});
- $executions[$executionID]->{$extendField->field} = htmlSpecialString($executions[$executionID]->{$extendField->field});
- }
- if(empty($executions[$executionID]->begin)) dao::$errors["begin[{$executionID}]"] = sprintf($this->lang->error->notempty, $this->lang->execution->begin);
- if(empty($executions[$executionID]->end)) dao::$errors["end[{$executionID}]"] = sprintf($this->lang->error->notempty, $this->lang->execution->end);
- /* Project begin and end check. */
- if(!empty($executions[$executionID]->begin) and !empty($executions[$executionID]->end))
- {
- if($executions[$executionID]->begin > $executions[$executionID]->end)
- {
- dao::$errors["end[{$executionID}]"] = sprintf($this->lang->execution->errorLesserPlan, $executions[$executionID]->end, $executions[$executionID]->begin);
- }
- if($project and $executions[$executionID]->begin < $project->begin)
- {
- dao::$errors["begin[{$executionID}]"] = sprintf($this->lang->execution->errorLesserProject, $project->begin);
- }
- if($project and $executions[$executionID]->end > $project->end)
- {
- dao::$errors["end[{$executionID}]"] = sprintf($this->lang->execution->errorGreaterProject, $project->end);
- }
- }
- }
- return $executions;
- }
- /**
- * 获取燃尽图相关数据。
- * Get burn related data.
- *
- * @param array $executionIdList
- * @access protected
- * @return array
- */
- protected function fetchBurnData($executionIdList)
- {
- $today = helper::today();
- $burns = $this->dao->select("execution, '$today' AS date, sum(estimate) AS `estimate`, sum(`left`) AS `left`, SUM(consumed) AS `consumed`")->from(TABLE_TASK)
- ->where('execution')->in($executionIdList)
- ->andWhere('deleted')->eq('0')
- ->andWhere('isParent')->eq('0')
- ->andWhere('status')->ne('cancel')
- ->groupBy('execution')
- ->fetchAll('execution');
- $closedLefts = $this->dao->select('execution, sum(`left`) AS `left`')->from(TABLE_TASK)
- ->where('execution')->in($executionIdList)
- ->andWhere('deleted')->eq('0')
- ->andWhere('isParent')->eq('0')
- ->andWhere('status')->eq('closed')
- ->groupBy('execution')
- ->fetchAll('execution');
- $finishedEstimates = $this->dao->select("execution, sum(`estimate`) AS `estimate`")->from(TABLE_TASK)
- ->where('execution')->in($executionIdList)
- ->andWhere('deleted')->eq('0')
- ->andWhere('isParent')->eq('0')
- ->andWhere('status', true)->eq('done')
- ->orWhere('status')->eq('closed')
- ->markRight(1)
- ->groupBy('execution')
- ->fetchAll('execution');
- $storyPoints = $this->dao->select('t1.project, sum(t2.estimate) AS `storyPoint`')->from(TABLE_PROJECTSTORY)->alias('t1')
- ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
- ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t2.product = t3.id')
- ->where('t1.project')->in($executionIdList)
- ->andWhere('t2.deleted')->eq(0)
- ->andWhere('t2.status')->ne('closed')
- ->andWhere('t2.stage')->in('wait,planned,projected,developing')
- ->andWhere('t2.isParent')->eq('0')
- ->groupBy('project')
- ->fetchAll('project');
- return array($burns, $closedLefts, $finishedEstimates, $storyPoints);
- }
- /**
- * 获取燃尽图数据。
- * Get burn data.
- *
- * @param int $executionID
- * @param string $date
- * @param int $taskCount
- * @access protected
- * @return object|bool
- */
- protected function getBurnByExecution($executionID, $date = '', $taskCount = 0)
- {
- return $this->dao->select('*')->from(TABLE_BURN)
- ->where('execution')->eq($executionID)
- ->beginIF($date)->andWhere('date')->eq($date)->fi()
- ->beginIF($taskCount)->andWhere('task')->eq($taskCount)->fi()
- ->fetch();
- }
- /**
- * 获取执行团队成员数量。
- * Get execution team member count.
- *
- * @param array $executionIdList
- * @access protected
- * @return void
- */
- protected function getMemberCountGroup($executionIdList)
- {
- return $this->dao->select('t1.root,count(t1.id) as teams')->from(TABLE_TEAM)->alias('t1')
- ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account=t2.account')
- ->where('t1.root')->in($executionIdList)
- ->andWhere('t1.type')->ne('project')
- ->andWhere('t2.deleted')->eq(0)
- ->groupBy('t1.root')
- ->fetchAll('root');
- }
- /**
- * 通过产品ID列表获取执行id:name的键值对。
- * Get the pair for execution id:name from the product id list.
- *
- * @param array $productIdList
- * @access protected
- * @return array
- */
- protected function getPairsByProduct($productIdList)
- {
- return $this->dao->select('t1.project, t2.name')->from(TABLE_PROJECTPRODUCT)->alias('t1')
- ->leftJoin(TABLE_EXECUTION)->alias('t2')
- ->on('t1.project = t2.id')
- ->where('t1.product')->in($productIdList)
- ->andWhere('t2.type')->in('sprint,stage,kanban')
- ->fetchPairs('project');
- }
- /**
- * 获取执行关联的产品信息。
- * Get product information of the linked execution.
- *
- * @param int $executionID
- * @access protected
- * @return array
- */
- protected function getProductList($executionID)
- {
- $executions = $this->dao->select('t1.id,t2.product,t3.name')->from(TABLE_EXECUTION)->alias('t1')
- ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.id=t2.project')
- ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t2.product=t3.id')
- ->where('t1.project')->eq($executionID)
- ->andWhere('t1.type')->in('kanban,sprint,stage')
- ->fetchAll();
- $productList = array();
- $linkedProducts = array();
- foreach($executions as $execution)
- {
- if(!isset($productList[$execution->id]))
- {
- $productList[$execution->id] = new stdclass();
- $productList[$execution->id]->product = '';
- $productList[$execution->id]->productName = '';
- }
- if(isset($linkedProducts[$execution->id][$execution->product])) continue;
- $productList[$execution->id]->product .= $execution->product . ',';
- $productList[$execution->id]->productName .= $execution->name . ',';
- $linkedProducts[$execution->id][$execution->product] = $execution->product;
- }
- return $productList;
- }
- /**
- * 通过搜索获取Bug列表数据。
- * Get bugs by search in execution.
- *
- * @param array $productIdList
- * @param int $executionID
- * @param string $sql
- * @param string $orderBy
- * @param object|null $pager
- * @access public
- * @return object[]
- */
- public function getSearchBugs($productIdList, $executionID, $sql = '1=1', $orderBy = 'id_desc', $pager = null)
- {
- return $this->dao->select('*')->from(TABLE_BUG)
- ->where($sql)
- ->andWhere('status')->eq('active')
- ->andWhere('toTask')->eq(0)
- ->andWhere('tostory')->eq(0)
- ->beginIF(!empty($productIdList))->andWhere('product')->in($productIdList)->fi()
- ->beginIF(empty($productIdList))->andWhere('execution')->eq($executionID)->fi()
- ->andWhere('deleted')->eq(0)
- ->orderBy($orderBy)
- ->page($pager)
- ->fetchAll('id');
- }
- /**
- * 将执行ID保存到session中。
- * Save the execution ID to the session.
- *
- * @param int $executionID
- * @access protected
- * @return void
- */
- protected function saveSession($executionID)
- {
- $this->session->set('execution', $executionID, $this->app->tab);
- $this->setProjectSession($executionID);
- }
- /**
- * 设置看板执行的菜单。
- * Set kanban menu.
- *
- * @access public
- * @return void
- */
- public function setKanbanMenu()
- {
- global $lang;
- $lang->executionCommon = $lang->execution->kanban;
- include $this->app->getModulePath('', 'execution') . 'lang/' . $this->app->getClientLang() . '.php';
- $this->lang->execution->menu = new stdclass();
- $this->lang->execution->menu->kanban = array('link' => "{$this->lang->kanban->common}|execution|kanban|executionID=%s", 'subModule' => 'task', 'alias' => 'importtask');
- $this->lang->execution->menu->CFD = array('link' => "{$this->lang->execution->CFD}|execution|cfd|executionID=%s");
- $this->lang->execution->menu->build = array('link' => "{$this->lang->build->common}|execution|build|executionID=%s", 'alias' => 'bug', 'subModule' => 'projectbuild,build,bug');
- $this->lang->execution->menu->settings = array('link' => "{$this->lang->settings}|execution|view|executionID=%s", 'subModule' => 'personnel', 'alias' => 'edit,manageproducts,team,whitelist,addwhitelist,managemembers', 'class' => 'dropdown dropdown-hover');
- $this->lang->execution->dividerMenu = '';
- $this->lang->execution->menu->settings['subMenu'] = new stdclass();
- $this->lang->execution->menu->settings['subMenu']->view = array('link' => "{$this->lang->overview}|execution|view|executionID=%s", 'subModule' => 'view', 'alias' => 'edit,start,suspend,putoff,close');
- $this->lang->execution->menu->settings['subMenu']->products = array('link' => "{$this->lang->productCommon}|execution|manageproducts|executionID=%s");
- $this->lang->execution->menu->settings['subMenu']->team = array('link' => "{$this->lang->team->common}|execution|team|executionID=%s", 'alias' => 'managemembers');
- $this->lang->execution->menu->settings['subMenu']->whitelist = array('link' => "{$this->lang->whitelist}|execution|whitelist|executionID=%s", 'subModule' => 'personnel', 'alias' => 'addwhitelist');
- }
- /**
- * 更新今日的累计流图数据。
- * Update today's cumulative flow graph data.
- *
- * @param int $executionID
- * @param string $type
- * @param string $colName
- * @param array $laneGroup
- * @access protected
- * @return void
- */
- protected function updateTodayCFDData($executionID, $type, $colName, $laneGroup)
- {
- $cfd = new stdclass();
- $cfd->count = 0;
- $cfd->date = helper::today();
- $cfd->type = $type;
- foreach($laneGroup as $columnGroup)
- {
- foreach($columnGroup as $columnCard)
- {
- $cards = trim($columnCard->cards, ',');
- $cfd->count += $cards ? count(explode(',', $cards)) : 0;
- }
- }
- $cfd->name = $colName;
- $cfd->execution = $executionID;
- $this->dao->replace(TABLE_CFD)->data($cfd)->exec();
- }
- /**
- * 获取任务分组数据。
- * Get task group data.
- *
- * @param int $executionID
- * @access protected
- * @return array
- */
- protected function getTaskGroups($executionID)
- {
- $executionID = (int)$executionID;
- $tasks = $this->dao->select('*')->from(TABLE_TASK)
- ->where('execution')->eq($executionID)
- ->andWhere('deleted')->eq(0)
- ->andWhere('parent')->eq(0)
- ->orderBy('id_desc')
- ->fetchAll();
- $childTasks = $this->dao->select('*')->from(TABLE_TASK)
- ->where('execution')->eq($executionID)
- ->andWhere('deleted')->eq(0)
- ->andWhere('parent')->ne(0)
- ->orderBy('id_desc')
- ->fetchGroup('parent');
- $taskGroups = array();
- foreach($tasks as $task) $taskGroups[$task->module][$task->story][$task->id] = $this->appendTaskChildren($task, $childTasks);
- return $taskGroups;
- }
- /**
- * 递归获取任务数量。
- * Get task count.
- *
- * @param array $tasks
- * @access protected
- * @return int
- *
- */
- protected function getTaskCount($tasks)
- {
- $taskCount = count($tasks);
- foreach($tasks as $task)
- {
- if(!empty($task->children)) $taskCount += $this->getTaskCount($task->children);
- }
- return $taskCount;
- }
- /**
- * 追加子任务到任务
- * Append children to task
- *
- * @param object $task
- * @param array $childTasks
- * @return object
- */
- protected function appendTaskChildren($task, $childTasks)
- {
- if(empty($childTasks[$task->id])) return $task;
- $task->children = array();
- foreach($childTasks[$task->id] as $child)
- {
- $child = $this->appendTaskChildren($child, $childTasks);
- $task->children[] = $child;
- }
- return $task;
- }
- /**
- * 处理树状图需求类型数据。
- * Process tree chart demand type data.
- *
- * @param object $node
- * @param array $storyGroups
- * @param array $taskGroups
- * @param int $executionID
- * @access protected
- * @return object
- */
- protected function processStoryNode($node, $storyGroups, $taskGroups, $executionID)
- {
- $node->type = 'module';
- $stories = isset($storyGroups[$node->root][$node->id]) ? $storyGroups[$node->root][$node->id] : array();
- $node->children = $this->buildStoryTree($stories, $taskGroups, $executionID, $node);
- /* Append for task of no story && node is not root. */
- if($node->id && isset($taskGroups[$node->id][0]))
- {
- $taskItems = $this->formatTasksForTree($taskGroups[$node->id][0]);
- $node->tasksCount = $this->getTaskCount($taskItems);
- foreach($taskItems as $taskItem) $node->children[] = $taskItem;
- }
- return $node;
- }
- /**
- * 构造需求树。
- * Build story tree.
- *
- * @param array $stories
- * @param array $taskGroups
- * @param int $executionID
- * @param object $node
- * @param int $parentID
- * @access protected
- * @return array
- */
- protected function buildStoryTree($stories, $taskGroups, $executionID, $node, $parentID = 0)
- {
- static $users, $avatarPairs;
- if(empty($users)) $users = $this->loadModel('user')->getPairs('noletter');
- if(empty($avatarPairs)) $avatarPairs = $this->loadModel('user')->getAvatarPairs();
- $children = array();
- foreach($stories as $story)
- {
- if($story->parent != $parentID && ($parentID || (!$parentID && isset($stories[$story->parent])))) continue;
- $avatarAccount = empty($story->assignedTo) ? zget($story, 'openedBy', '') : $story->assignedTo;
- $userAvatar = zget($avatarPairs, $avatarAccount);
- $userAvatar = $userAvatar && $avatarAccount != 'closed' ? "<img src='{$userAvatar}'/>" : strtoupper(mb_substr($avatarAccount, 0, 1, 'utf-8'));
- $storyItem = new stdclass();
- $storyItem->type = $story->type;
- $storyItem->id = 'story' . $story->id;
- $storyItem->title = $story->title;
- $storyItem->color = $story->color;
- $storyItem->pri = $story->pri;
- $storyItem->storyId = $story->id;
- $storyItem->grade = $story->grade;
- $storyItem->openedBy = zget($users, $story->openedBy);
- $storyItem->assignedTo = zget($users, $story->assignedTo);
- $storyItem->url = helper::createLink('execution', 'storyView', "storyID=$story->id&execution=$executionID");
- $storyItem->taskCreateUrl = helper::createLink('task', 'batchCreate', "executionID={$executionID}&story={$story->id}");
- $storyItem->avatarAccount = zget($users, $avatarAccount);
- $storyItem->avatar = $userAvatar;
- $storyTasks = isset($taskGroups[$node->id][$story->id]) ? $taskGroups[$node->id][$story->id] : array();
- if(!empty($storyTasks))
- {
- $taskItems = $this->formatTasksForTree($storyTasks, $stories);
- $storyItem->tasksCount = $this->getTaskCount($taskItems);
- $storyItem->children = $taskItems;
- }
- else
- {
- $storyItemChildren = $this->buildStoryTree($stories, $taskGroups, $executionID, $node, $story->id);
- if($storyItemChildren) $storyItem->children = $storyItemChildren;
- }
- $children[] = $storyItem;
- }
- return $children;
- }
- /**
- * 处理树状图任务类型数据。
- * Process tree chart task type data.
- *
- * @param object $node
- * @param array $taskGroups
- * @access protected
- * @return object
- */
- protected function processTaskNode($node, $taskGroups)
- {
- $node->type = 'module';
- $node->tasksCount = 0;
- if(!isset($taskGroups[$node->id])) return $node;
- foreach($taskGroups[$node->id] as $tasks)
- {
- $taskItems = $this->formatTasksForTree($tasks);
- $node->tasksCount += $this->getTaskCount($taskItems);
- foreach($taskItems as $taskItem) $node->children[$taskItem->id] = $taskItem;
- }
- $node->children = array_values($node->children);
- return $node;
- }
- /**
- * Update team of execution when it edited.
- *
- * @param int $executionID
- * @param object $oldExecution
- * @param object $execution
- * @return void
- */
- protected function updateTeam($executionID, $oldExecution, $execution)
- {
- if(defined('RUN_MODE') && RUN_MODE == 'api') return;
- /* Get team and language item. */
- $this->loadModel('user');
- $team = $this->user->getTeamMemberPairs($executionID, 'execution');
- $members = isset($_POST['teamMembers']) ? $_POST['teamMembers'] : array();
- array_push($members, $execution->PO, $execution->QD, $execution->PM, $execution->RD);
- $members = array_unique($members);
- $roles = $this->user->getUserRoles(array_values($members));
- /* Add the member who joined the team. */
- $changedAccounts = array();
- $teamMembers = array();
- foreach($members as $account)
- {
- if(empty($account) or isset($team[$account])) continue;
- $member = new stdclass();
- $member->root = $executionID;
- $member->account = $account;
- $member->join = helper::today();
- $member->role = zget($roles, $account, '');
- $member->days = zget($execution, 'days', 0);
- $member->type = 'execution';
- $member->hours = $this->config->execution->defaultWorkhours;
- $this->dao->replace(TABLE_TEAM)->data($member)->exec();
- $changedAccounts[$account] = $account;
- $teamMembers[$account] = $member;
- }
- /* Remove the member who left the team. */
- $this->dao->delete()->from(TABLE_TEAM)
- ->where('root')->eq($executionID)
- ->andWhere('type')->eq('execution')
- ->andWhere('account')->in(array_keys($team))
- ->andWhere('account')->notin(array_values($members))
- ->andWhere('account')->ne($oldExecution->openedBy)
- ->exec();
- if(isset($execution->project) and $execution->project) $this->addProjectMembers((int)$execution->project, $teamMembers);
- /* Fix bug#3074, Update views for team members. */
- if($execution->acl != 'open') $this->updateUserView($executionID, 'sprint', $changedAccounts);
- }
- /**
- * 格式化任务树的数据。
- * Format tasks for tree.
- *
- * @param array $tasks
- * @param array $stories
- * @access protected
- * @return array
- */
- protected function formatTasksForTree($tasks, $stories = array())
- {
- static $users, $avatarPairs;
- if(empty($users)) $users = $this->loadModel('user')->getPairs('noletter');
- if(empty($avatarPairs)) $avatarPairs = $this->loadModel('user')->getAvatarPairs();
- $taskItems = array();
- foreach($tasks as $task)
- {
- $avatarAccount = empty($task->assignedTo) ? zget($task, 'openedBy', '') : $task->assignedTo;
- $userAvatar = zget($avatarPairs, $avatarAccount);
- $userAvatar = $userAvatar && $avatarAccount != 'closed' ? "<img src='{$userAvatar}' />" : strtoupper(mb_substr($avatarAccount, 0, 1, 'utf-8'));
- $story = zget($stories, $task->story, '');
- $taskItem = new stdclass();
- $taskItem->type = 'task';
- $taskItem->id = $task->id;
- $taskItem->title = $task->name;
- $taskItem->color = $task->color;
- $taskItem->pri = (int)$task->pri;
- $taskItem->status = $task->status;
- $taskItem->parent = $task->parent;
- $taskItem->isParent = $task->isParent;
- $taskItem->estStarted = $task->estStarted;
- $taskItem->realStarted = $task->realStarted;
- $taskItem->estimate = $task->estimate;
- $taskItem->consumed = $task->consumed;
- $taskItem->left = $task->left;
- $taskItem->openedBy = zget($users, $task->openedBy);
- $taskItem->assignedTo = zget($users, $task->assignedTo);
- $taskItem->url = helper::createLink('task', 'view', "task=$task->id");
- $taskItem->storyChanged = $story && $story->status == 'active' && $story->version > $story->taskVersion;
- $taskItem->avatarAccount = zget($users, $avatarAccount);
- $taskItem->avatar = $userAvatar;
- if(!empty($task->children)) $taskItem->children = $this->formatTasksForTree($task->children, $stories);
- $taskItems[] = $taskItem;
- }
- return $taskItems;
- }
- /**
- * 添加执行的团队。
- * Add execution members.
- *
- * @param int $executionID
- * @param array $postMembers e.g. array('admin', 'dev1')
- * @access protected
- * @return void
- */
- protected function addExecutionMembers($executionID, $postMembers)
- {
- $execution = $this->fetchByID($executionID);
- if(empty($execution)) return;
- array_push($postMembers, $execution->PO, $execution->QD, $execution->PM, $execution->RD, $execution->openedBy);
- $members = array_filter(array_unique($postMembers));
- $roles = $this->loadModel('user')->getUserRoles(array_values($members));
- $today = helper::today();
- $teamMembers = array();
- $oldTeams = $this->dao->select('account')->from(TABLE_TEAM)->where('root')->eq($executionID)->andWhere('type')->eq('execution')->fetchPairs();
- foreach($members as $account)
- {
- if(isset($oldTeams[$account])) continue;
- $member = new stdClass();
- $member->root = $executionID;
- $member->type = 'execution';
- $member->account = $account;
- $member->role = zget($roles, $account, '');
- $member->join = $today;
- $member->days = $execution->days;
- $member->hours = $this->config->execution->defaultWorkhours;
- $this->dao->insert(TABLE_TEAM)->data($member)->exec();
- $teamMembers[$account] = $member;
- }
- if($execution->acl != 'open') $this->updateUserView($executionID, 'sprint');
- $this->addProjectMembers($execution->project, $teamMembers);
- }
- /**
- * 关联创建执行主库
- * Create main lib for product
- *
- * @param int productID
- * @access protected
- * @return int|false
- */
- protected function createMainLib($projectID, $executionID, $type = 'sprint')
- {
- if($projectID < 0 || $executionID <= 0) return false;
- $existedLibID = (int)$this->dao->select('id')->from(TABLE_DOCLIB)->where('execution')->eq($executionID)
- ->andWhere('type')->eq('execution')
- ->andWhere('main')->eq('1')
- ->fetch('id');
- if($existedLibID) return $existedLibID;
- $this->app->loadLang('doc');
- if($type == 'sprint') $this->app->loadLang('project');
- $lib = new stdclass();
- $lib->project = $projectID;
- $lib->execution = $executionID;
- $lib->name = $type == 'stage' ? str_replace($this->lang->executionCommon, $this->lang->project->stage, $this->lang->doclib->main['execution']) : $this->lang->doclib->main['execution'];
- $lib->type = 'execution';
- $lib->main = '1';
- $lib->acl = 'default';
- $lib->addedBy = $this->app->user->account;
- $lib->addedDate = helper::now();
- $this->dao->insert(TABLE_DOCLIB)->data($lib)->exec();
- if(dao::isError()) return false;
- return $this->dao->lastInsertID();
- }
- }
|