| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197 |
- <?php
- /**
- * The tao file of task 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 task
- * @link https://www.zentao.net
- */
- class taskTao extends taskModel
- {
- /**
- * 计算任务列表中每个任务的进度,包括子任务。
- * Compute progress of task list, include its' children.
- *
- * @param object[] $tasks
- * @access protected
- * @return object[]
- */
- protected function batchComputeProgress($tasks)
- {
- foreach($tasks as $task)
- {
- $task->progress = $this->computeTaskProgress($task);
- if(empty($task->children)) continue;
- $task->children = $this->batchComputeProgress($task->children);
- }
- return $tasks;
- }
- /**
- * 根据填写的日志,记录历史记录、改变任务的状态、消耗工时等信息并返回。
- * According to the effort, record the history, change the status of the task, consumed hours and other information and return.
- *
- * @param object $record
- * @param object $task
- * @param string $lastDate
- * @param bool $isFinishTask
- * @access protected
- * @return array
- */
- protected function buildTaskForEffort($record, $task, $lastDate, $isFinishTask)
- {
- $this->loadModel('action');
- $now = helper::now();
- $actionID = 0;
- $newTask = clone $task;
- $newTask->consumed += $record->consumed;
- $newTask->lastEditedBy = $this->app->user->account;
- $newTask->lastEditedDate = $now;
- if(!$task->realStarted) $newTask->realStarted = $now;
- if($lastDate <= $record->date) $newTask->left = $record->left;
- /* Finish task by effort. */
- if(!$newTask->left && $isFinishTask)
- {
- $newTask->status = 'done';
- $newTask->assignedTo = $task->openedBy;
- $newTask->assignedDate = $now;
- $newTask->finishedBy = $this->app->user->account;
- $newTask->finishedDate = $now;
- $actionID = $this->action->create('task', $task->id, 'Finished', $record->work);
- }
- /* Start task by effort. */
- elseif($newTask->status == 'wait')
- {
- $newTask->status = 'doing';
- $newTask->assignedTo = $this->app->user->account;
- $newTask->assignedDate = $now;
- $actionID = $this->action->create('task', $task->id, 'Started', $record->work);
- }
- /* Activate task by effort. */
- elseif($newTask->left != 0 && strpos('done,pause,cancel,closed,pause', $task->status) !== false)
- {
- $newTask->status = 'doing';
- $newTask->assignedTo = $this->app->user->account;
- $newTask->finishedBy = '';
- $newTask->canceledBy = '';
- $newTask->closedBy = '';
- $newTask->closedReason = '';
- $newTask->finishedDate = null;
- $newTask->canceledDate = null;
- $newTask->closedDate = null;
- $actionID = $this->action->create('task', $task->id, 'Activated', $record->work);
- }
- else
- {
- $actionID = $this->action->create('task', $task->id, 'RecordWorkhour', $record->work, (float)$record->consumed);
- }
- return array($newTask, $actionID);
- }
- /**
- * 编辑日志后,构造待更新的任务数据。
- * After editing the effort, build the task data to be updated.
- *
- * @param object $task
- * @access protected
- * @return array|false
- * @param object $oldEffort
- * @param object $effort
- */
- protected function buildTaskForUpdateEffort($task, $oldEffort, $effort)
- {
- $lastEffort = $this->dao->select('*')->from(TABLE_EFFORT)
- ->where('objectID')->eq($task->id)
- ->andWhere('objectType')->eq('task')
- ->orderBy('date_desc,id_desc')->limit(1)->fetch();
- $consumed = $task->consumed + $effort->consumed - $oldEffort->consumed;
- $left = ($lastEffort && $effort->id == $lastEffort->id) ? $effort->left : $lastEffort->left;
- $now = helper::now();
- $data = new stdclass();
- $data->consumed = $consumed;
- $data->left = $left;
- $data->status = $task->status;
- $data->lastEditedBy = $this->app->user->account;
- $data->lastEditedDate = $now;
- if(empty($left) && strpos('wait,doing,pause', $task->status) !== false)
- {
- $data->status = 'done';
- $data->finishedBy = $this->app->user->account;
- $data->finishedDate = $now;
- $data->assignedTo = $task->openedBy;
- }
- return $data;
- }
- /**
- * 将任务的层级改为父子结构。
- * Change the hierarchy of tasks to a parent-child structure.
- *
- * @param object[] $tasks
- * @param object[] $parentTasks
- * @access protected
- * @return object[]
- */
- protected function buildTaskTree($tasks, $parentTasks)
- {
- foreach($tasks as $task)
- {
- /* 如果不是父任务则跳过。*/
- if($task->parent <= 0) continue;
- if(isset($tasks[$task->parent]))
- {
- /* 如果任务列表里有这个任务的父任务,则将子任务放到父任务里,并删除子任务。*/
- if(!isset($tasks[$task->parent]->children)) $tasks[$task->parent]->children = array();
- $tasks[$task->parent]->children[$task->id] = $task;
- unset($tasks[$task->id]);
- }
- else
- {
- /* 如果任务列表里没有这个任务的父任务,则从父任务列表获取到父任务名称并附加到子任务上。*/
- $parent = $parentTasks[$task->parent];
- $task->parentName = $parent->name;
- }
- }
- return $tasks;
- }
- /**
- * 构造日志数据,加上任务ID、记录人字段。
- * Add fields to workhour.
- *
- * @param int $taskID
- * @param array $workhour
- * @access protected
- * @return array
- */
- protected function buildWorkhour($taskID, $workhour)
- {
- foreach($workhour as $record)
- {
- $record->task = $taskID;
- $record->account = $this->app->user->account;
- }
- return $workhour;
- }
- /**
- * 编辑日志时,检查输入是否合法。
- * When editing a effort, check that the input is legal.
- *
- * @param object $effort
- * @access protected
- * @return bool
- */
- protected function checkEffort($effort)
- {
- $today = helper::today();
- if(helper::isZeroDate($effort->date)) dao::$errors['date'] = $this->lang->task->error->dateEmpty;
- if($effort->date > $today) dao::$errors['date'] = $this->lang->task->error->date;
- if($effort->consumed <= 0) dao::$errors['comsumed'] = sprintf($this->lang->error->gt, $this->lang->task->record, '0');
- if($effort->left < 0) dao::$errors['left'] = sprintf($this->lang->error->ge, $this->lang->task->left, '0');
- return !dao::isError();
- }
- /**
- * 检查一个任务是否有子任务。
- * Check if a task has children.
- *
- * @param int $taskID
- * @access protected
- * @return bool
- */
- protected function checkHasChildren($taskID)
- {
- $childrenCount = $this->dao->select('COUNT(1) AS count')->from(TABLE_TASK)->where('parent')->eq($taskID)->fetch('count');
- if(!$childrenCount) return false;
- return true;
- }
- /**
- * 获取任务的进度,通过任务的消耗和剩余工时计算,结果以百分比的数字部分显示。
- * Compute progress of a task.
- *
- * @param object $task
- * @access protected
- * @return float
- */
- protected function computeTaskProgress($task)
- {
- if($task->left > 0) return round($task->consumed / ((float)$task->consumed + (float)$task->left), 2) * 100;
- if($task->consumed == 0) return 0;
- return 100;
- }
- /**
- * 计算当前任务的状态。
- * Compute the status of the current task.
- *
- * @param object $currentTask
- * @param object $oldTask
- * @param object $task
- * @param bool $autoStatus true|false
- * @param bool $hasEfforts true|false
- * @param array $members
- * @access protected
- * @return object
- */
- protected function computeTaskStatus($currentTask, $oldTask, $task, $autoStatus, $hasEfforts, $members)
- {
- /* If the status is not automatic, return the current task. */
- if(!$autoStatus) return $currentTask;
- /* If consumed of the current task is empty and current task has no efforts, the current task status should be wait. */
- if($currentTask->consumed == 0 && !$hasEfforts)
- {
- if(!isset($task->status)) $currentTask->status = 'wait';
- $currentTask->finishedBy = '';
- $currentTask->finishedDate = null;
- }
- /* If neither consumed nor left of the current task is empty, the current task status should be doing. */
- if($currentTask->consumed > 0 && $currentTask->left > 0)
- {
- $currentTask->status = 'doing';
- $currentTask->finishedBy = '';
- $currentTask->finishedDate = null;
- }
- /* If consumed of the current task is not empty and left of the current task is empty, the current task status should be done or doing. */
- if($currentTask->consumed > 0 && $currentTask->left == 0)
- {
- $finishedUsers = $this->getFinishedUsers($oldTask->id, $members);
- /* If the number of finisher is less than the number of team members , the current task status should be doing. */
- if(count($finishedUsers) != count($members))
- {
- if(strpos('cancel,pause', $oldTask->status) === false || ($oldTask->status == 'closed' && $oldTask->reason == 'done'))
- {
- $currentTask->status = 'doing';
- $currentTask->finishedBy = '';
- $currentTask->finishedDate = null;
- }
- }
- /* If status of old task is wait or doing or pause, the current task status should be done. */
- elseif(strpos('wait,doing,pause', $oldTask->status) !== false)
- {
- $currentTask->status = 'done';
- $currentTask->assignedTo = $oldTask->openedBy;
- $currentTask->assignedDate = helper::now();
- $currentTask->finishedBy = $this->app->user->account;
- $currentTask->finishedDate = zget($task, 'finishedDate', null);
- }
- }
- return $currentTask;
- }
- /**
- * 拼接团队成员信息,包括账号、预计、消耗、剩余,用来创建历史记录。例如:团队成员: admin, 预计: 2, 消耗: 0, 剩余: 3。
- * Concat team info for create history.
- *
- * @param array $teamInfoList
- * @param array $userPairs
- * @access protected
- * @return string
- */
- protected function concatTeamInfo($teamInfoList, $userPairs)
- {
- $teamInfo = '';
- foreach($teamInfoList as $info) $teamInfo .= "{$this->lang->task->teamMember}: " . zget($userPairs, $info->account) . ", {$this->lang->task->estimateAB}: " . (float)$info->estimate . ", {$this->lang->task->consumedAB}: " . (float)$info->consumed . ", {$this->lang->task->leftAB}: " . (float)$info->left . "\n";
- return $teamInfo;
- }
- /**
- * 复制任务数据。
- * Copy the task data and update the effort to the new task.
- *
- * @param object $parentTask
- * @access public
- * @return bool|int
- * @param object $task
- */
- public function copyTaskData($task)
- {
- /* 复制当前任务信息。 */
- /* Copy the current task to child task, and change the parent field value. */
- $copyTask = clone $task;
- $copyTask->parent = $task->id;
- $copyTask->feedback = 0;
- unset($copyTask->id);
- if($this->config->edition != 'open')
- {
- $fieldList = $this->loadModel('workflowfield')->getList('task');
- foreach($fieldList as $field)
- {
- if($field->type == 'date' || $field->type == 'datetime') $this->config->task->dateFields[] = $field->field;
- }
- }
- foreach($this->config->task->dateFields as $dateField)
- {
- if(empty($copyTask->$dateField)) unset($copyTask->$dateField);
- }
- $this->dao->insert(TABLE_TASK)->data($copyTask)->autoCheck()->exec();
- $copyTaskID = $this->dao->lastInsertID();
- $copyTaskPath = $task->path . "{$copyTaskID},";
- $this->dao->update(TABLE_TASK)->set('path')->eq($copyTaskPath)->where('id')->eq($copyTaskID)->exec();
- if(dao::isError()) return false;
- $path = $copyTask->path . "{$copyTaskID},";
- $this->dao->update(TABLE_TASK)->set('path')->eq($path)->where('id')->eq($copyTaskID)->exec();
- if(dao::isError()) return false;
- /* 将父任务的日志记录更新到子任务下。 */
- /* Update the logs of the parent task under the subtask. */
- $this->dao->update(TABLE_EFFORT)->set('objectID')->eq($copyTaskID)
- ->where('objectID')->eq($task->id)
- ->andWhere('objectType')->eq('task')
- ->exec();
- return !dao::isError() ? $copyTaskID : false;
- }
- /**
- * 多人任务的团队变更后,记录对比历史记录。
- * Update a task.
- *
- * @param object $oldTask
- * @param object $task
- * @access protected
- * @return bool
- */
- protected function createChangesForTeam($oldTask, $task)
- {
- $users = $this->loadModel('user')->getPairs('noletter|noempty');
- $oldTeams = $oldTask->team;
- $oldTeamInfo = array();
- foreach($oldTeams as $team) $oldTeamInfo[$team->account][] = "{$this->lang->task->teamMember}: " . zget($users, $team->account) . ", {$this->lang->task->estimateAB}: " . (float)$team->estimate . ", {$this->lang->task->consumedAB}: " . (float)$team->consumed . ", {$this->lang->task->leftAB}: " . (float)$team->left;
- $newTeamInfo = array();
- foreach($this->post->team as $i => $account)
- {
- if(empty($account)) continue;
- $newTeamInfo[$account][] = "{$this->lang->task->teamMember}: " . zget($users, $account) . ", {$this->lang->task->estimateAB}: " . zget($this->post->teamEstimate, $i, 0) . ", {$this->lang->task->consumedAB}: " . zget($this->post->teamConsumed, $i, 0) . ", {$this->lang->task->leftAB}: " . zget($this->post->teamLeft, $i, 0);
- }
- /* 只保留有变化的部分, 方便对比。*/
- $oldTask->team = '';
- $task->team = '';
- foreach($oldTeamInfo as $account => $infos)
- {
- foreach($infos as $index => $info)
- {
- if(isset($newTeamInfo[$account][$index]) && $newTeamInfo[$account][$index] == $info)
- {
- unset($newTeamInfo[$account][$index]);
- }
- else
- {
- $oldTask->team .= $info . "\n";
- }
- }
- }
- foreach($newTeamInfo as $account => $infos)
- {
- foreach($infos as $info)
- {
- $task->team .= $info . "\n";
- }
- }
- return array($oldTask, $task);
- }
- /**
- * 更新自动更新任务状态时记录修改日志。
- * Create action record when auto update task.
- *
- * @param object $oldTask
- * @param string $source parent|child
- * @access protected
- * @return void
- */
- protected function createAutoUpdateTaskAction($oldTask, $source = 'parent')
- {
- $newTask = $this->dao->select('*')->from(TABLE_TASK)->where('id')->eq($oldTask->id)->fetch();
- unset($oldTask->subStatus);
- unset($newTask->subStatus);
- $changes = common::createChanges($oldTask, $newTask);
- $action = '';
- if($newTask->status == 'done' && $oldTask->status != 'done') $action = 'Finished';
- if($newTask->status == 'closed' && $oldTask->status != 'closed') $action = 'Closed';
- if($newTask->status == 'pause' && $oldTask->status != 'pause') $action = 'Paused';
- if($newTask->status == 'cancel' && $oldTask->status != 'cancel') $action = 'Canceled';
- if($newTask->status == 'doing' && $oldTask->status == 'wait') $action = 'Started';
- if($newTask->status == 'doing' && $oldTask->status == 'pause') $action = 'Restarted';
- if($newTask->status == 'wait' && $oldTask->status != 'wait') $action = 'Adjusttasktowait';
- if($newTask->status == 'doing' && $oldTask->status != 'wait' && $oldTask->status != 'pause') $action = 'Activated';
- if(!$action) return;
- $actionID = $this->loadModel('action')->create('task', $oldTask->id, $action, '', 'autoby' . $source, '', false);
- $this->action->logHistory($actionID, $changes);
- }
- /**
- * 更新一个任务。
- * Update a task.
- *
- * @param object $task
- * @param object $oldTask
- * @param string $requiredFields
- * @access protected
- * @return bool
- */
- protected function doUpdate($task, $oldTask, $requiredFields)
- {
- /* Task link design. */
- if(!empty($task->design))
- {
- $design = $this->dao->select('version')->from(TABLE_DESIGN)->where('id')->eq($task->design)->fetch();
- $task->designVersion = $design->version;
- }
- $execution = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($task->execution)->fetch();
- if($this->isNoStoryExecution($execution)) $task->story = 0;
- /* Set the datetime and operator when the task is modified. */
- if(empty($task->lastEditedDate) || empty($task->lastEditedBy))
- {
- $task->lastEditedBy = $this->app->user->account;
- $task->lastEditedDate = helper::now();
- }
- if(isset($task->estimate)) $task->estimate = round((float)$task->estimate, 2);
- if(isset($task->consumed)) $task->consumed = round((float)$task->consumed, 2);
- if(isset($task->left)) $task->left = round((float)$task->left, 2);
- $this->dao->update(TABLE_TASK)->data($task, 'deleteFiles,renameFiles,files,docVersions,oldDocs,docs')
- ->autoCheck()
- ->batchCheckIF($task->status != 'cancel', $requiredFields, 'notempty')
- ->checkIF(!helper::isZeroDate($task->deadline), 'deadline', 'ge', $task->estStarted)
- ->batchCheckIF($task->status == 'wait' || $task->status == 'doing', 'finishedBy,finishedDate,canceledBy,canceledDate,closedBy,closedDate,closedReason', 'empty')
- ->checkIF($task->status == 'done', 'consumed', 'notempty')
- ->batchCheckIF($task->status == 'done', 'canceledBy,canceledDate', 'empty')
- ->batchCheckIF($task->closedReason == 'cancel', 'finishedBy,finishedDate,closedBy,closedDate,closedReason', 'empty')
- ->checkFlow()
- ->where('id')->eq($task->id)
- ->exec();
- return !dao::isError();
- }
- /**
- * 获取执行下的任务。
- * Fetch tasks under execution by executionID(Todo).
- *
- * @param int|array $executionID
- * @param int $productID
- * @param string|array $type all|assignedbyme|myinvolved|undone|needconfirm|assignedtome|finishedbyme|delayed|review|wait|doing|done|pause|cancel|closed|array('wait','doing','done','pause','cancel','closed')
- * @param array $modules
- * @param string $orderBy
- * @param object $pager
- * @access protected
- * @return object[]
- */
- protected function fetchExecutionTasks($executionID, $productID = 0, $type = 'all', $modules = array(), $orderBy = 'status_asc, id_desc', $pager = null)
- {
- if(is_string($type)) $type = strtolower($type);
- $orderBy = str_replace('pri_', 'priOrder_', $orderBy);
- $orderBy = str_replace('status', 'statusOrder', $orderBy);
- $fields = "DISTINCT t1.*, t2.id AS storyID, t2.title AS storyTitle, t2.product, t2.branch, t2.version AS latestStoryVersion, t2.status AS storyStatus, IF(t1.`pri` = 0, {$this->config->maxPriValue}, t1.`pri`) as priOrder, INSTR('wait,doing,done,pause,cancel,closed,', t1.status) as statusOrder";
- ($this->config->edition == 'max' or $this->config->edition == 'ipd') && $fields .= ', t5.name as designName, t5.version as latestDesignVersion';
- $actionIDList = array();
- if($type == 'assignedbyme') $actionIDList = $this->dao->select('objectID')->from(TABLE_ACTION)->where('objectType')->eq('task')->andWhere('action')->eq('assigned')->andWhere('actor')->eq($this->app->user->account)->fetchPairs('objectID', 'objectID');
- if(is_numeric($executionID) && $executionID) $execution = $this->fetchByID($executionID, 'project');
- $tasks = $this->dao->select($fields)
- ->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
- ->leftJoin(TABLE_TASKTEAM)->alias('t3')->on('t3.task = t1.id')
- ->beginIF($productID)->leftJoin(TABLE_MODULE)->alias('t4')->on('t1.module = t4.id')->fi()
- ->beginIF($this->config->edition == 'max' or $this->config->edition == 'ipd')->leftJoin(TABLE_DESIGN)->alias('t5')->on('t1.design= t5.id')->fi()
- ->where('t1.execution')->in($executionID)
- ->beginIF(is_numeric($executionID) && !empty($execution->isTpl))->andWhere('t1.isTpl')->eq('1')->fi()
- ->beginIF($type == 'myinvolved')
- ->andWhere("((t3.`account` = '{$this->app->user->account}') OR t1.`assignedTo` = '{$this->app->user->account}' OR t1.`finishedby` = '{$this->app->user->account}')")
- ->fi()
- ->beginIF($productID)->andWhere("((t4.root=" . (int)$productID . " and t4.type='story') OR t2.product=" . (int)$productID . ")")->fi()
- ->beginIF($type == 'undone')->andWhere('t1.status')->notIN('done,closed')->fi()
- ->beginIF($type == 'needconfirm')->andWhere('t2.version > t1.storyVersion')->andWhere("t2.status = 'active'")->fi()
- ->beginIF($type == 'assignedtome')->andWhere("(t1.assignedTo = '{$this->app->user->account}' or (t1.mode = 'multi' and t3.`account` = '{$this->app->user->account}' and t1.status != 'closed' and t3.status != 'done') )")->fi()
- ->beginIF($type == 'finishedbyme')
- ->andWhere('t1.finishedby', 1)->eq($this->app->user->account)
- ->orWhere('t3.status')->eq("done")
- ->markRight(1)
- ->fi()
- ->beginIF($type == 'delayed')->andWhere('t1.deadline')->gt('1970-1-1')->andWhere('t1.deadline')->lt(date(DT_DATE1))->andWhere('t1.status')->in('wait,doing')->fi()
- ->beginIF(is_array($type) or strpos(',all,undone,needconfirm,assignedtome,delayed,finishedbyme,myinvolved,assignedbyme,review,', ",$type,") === false)->andWhere('t1.status')->in($type)->fi()
- ->beginIF($modules)->andWhere('t1.module')->in($modules)->fi()
- ->beginIF($type == 'assignedbyme')->andWhere('t1.id')->in($actionIDList)->andWhere('t1.status')->ne('closed')->fi()
- ->beginIF($type == 'review')
- ->andWhere("FIND_IN_SET('{$this->app->user->account}', t1.reviewers)")
- ->andWhere('t1.reviewStatus')->eq('doing')
- ->fi()
- ->andWhere('t1.deleted')->eq(0)
- ->orderBy($orderBy)
- ->page($pager, 't1.id')
- ->fetchAll('id', false);
- $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'task', !($productID || in_array($type, array('myinvolved', 'needconfirm', 'assignedtome', 'finishedbyme'))));
- return $tasks;
- }
- /**
- * 通过任务类型查找用户的任务。
- * Fetch user tasks by type.
- *
- * @param string $account
- * @param string $type assignedTo|finishedBy|closedBy
- * @param string $orderBy
- * @param int $projectID
- * @param int $limit
- * @param object|null $pager
- * @access protected
- * @return object[]
- */
- protected function fetchUserTasksByType($account, $type, $orderBy, $projectID, $limit, $pager)
- {
- $orderBy = str_replace('pri_', 'priOrder_', $orderBy);
- $orderBy = str_replace('project_', 't1.project_', $orderBy);
- return $this->dao->select("t1.*, t4.id AS project, t2.id AS executionID, t2.name AS executionName, t4.name AS projectName, t2.multiple AS executionMultiple, t2.type AS executionType, t3.id AS storyID, t3.title AS storyTitle, t3.status AS storyStatus, t3.version AS latestStoryVersion, IF(t1.`pri` = 0, {$this->config->maxPriValue}, t1.`pri`) AS priOrder")
- ->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')
- ->leftJoin(TABLE_STORY)->alias('t3')->on('t1.story = t3.id')
- ->leftJoin(TABLE_PROJECT)->alias('t4')->on('t2.project = t4.id')
- ->leftJoin(TABLE_TASKTEAM)->alias('t5')->on("t5.task = t1.id and t5.account = '{$account}'")
- ->where('t1.deleted')->eq(0)
- ->andWhere('t2.deleted')->eq(0)
- ->andWhere('t4.deleted')->eq(0)
- ->beginIF($this->config->vision)->andWhere('t1.vision')->eq($this->config->vision)->fi()
- ->beginIF($this->config->vision)->andWhere('t2.vision')->eq($this->config->vision)->fi()
- ->beginIF($type != 'closedBy' && $this->app->moduleName == 'block')->andWhere('t1.status')->ne('closed')->fi()
- ->beginIF($projectID)->andWhere('t1.project')->eq($projectID)->fi()
- ->beginIF(!$this->app->user->admin)->andWhere('t1.execution')->in($this->app->user->view->sprints)->fi()
- ->beginIF($type == 'finishedBy')
- ->andWhere('t1.finishedby', 1)->eq($account)
- ->orWhere('t5.status')->eq('done')
- ->markRight(1)
- ->fi()
- ->beginIF($type == 'assignedTo' && ($this->app->rawModule == 'my' || $this->app->rawModule == 'block'))->andWhere('t2.status', true)->ne('suspended')->orWhere('t4.status')->ne('suspended')->markRight(1)->fi()
- ->beginIF(!in_array($type, array('all', 'finishedBy', 'assignedTo', 'myInvolved')))->andWhere("t1.`$type`")->eq($account)->fi()
- ->beginIF($type == 'assignedTo')->andWhere("((t1.assignedTo = '{$account}') or (t1.mode = 'multi' and t5.`account` = '{$account}' and t1.status != 'closed' and t5.status != 'done') )")->fi()
- ->beginIF($type == 'assignedTo' && $this->app->rawModule == 'my' && $this->app->rawMethod == 'work')->andWhere('t1.status')->notin('closed,cancel')->fi()
- ->beginIF($type == 'myInvolved')
- ->andWhere("((t5.`account` = '{$this->app->user->account}') OR t1.`assignedTo` = '{$this->app->user->account}' OR t1.`finishedby` = '{$this->app->user->account}')")
- ->fi()
- ->orderBy($orderBy)
- ->beginIF($limit > 0)->limit($limit)->fi()
- ->page($pager, 't1.id')
- ->fetchAll('id', false);
- }
- /**
- * Format the task with valid datetime that set the zero date or datetime to null.
- *
- * @param object $task
- * @return object
- */
- protected function formatDatetime($task)
- {
- if(empty($task)) return $task;
- if($this->config->edition != 'open')
- {
- $fieldList = $this->loadModel('workflowfield')->getList('task');
- foreach($fieldList as $field)
- {
- if($field->type == 'date' || $field->type == 'datetime') $this->config->task->dateFields[] = $field->field;
- }
- }
- foreach($task as $key => $value)
- {
- if(!in_array($key, $this->config->task->dateFields)) continue;
- if(!empty($value) && is_string($value) && helper::isZeroDate($value)) $task->$key = null;
- if(isset($task->$key) && empty($value)) $task->$key = null;
- }
- return $task;
- }
- /**
- * 获取多人任务的完成者。
- * Get the users who finished the multiple task.
- *
- * @param int $taskID
- * @param array $team
- * @access protected
- * @return array
- */
- protected function getFinishedUsers($taskID = 0, $team = array())
- {
- return $this->dao->select('id,account')->from(TABLE_TASKTEAM)
- ->where('task')->eq($taskID)
- ->andWhere('status')->in('done,closed')
- ->beginIF($team)->andWhere('account')->in($team)->fi()
- ->fetchPairs('id', 'account');
- }
- /**
- * Get left workhour of task after deleting a workhour of task.
- *
- * @param object $effort
- * @param object $task
- * @return float
- */
- protected function getLeftAfterDeleteWorkhour($effort, $task)
- {
- $left = $task->left;
- if($effort->isLast)
- {
- $lastTwoEfforts = $this->dao->select('`left`')->from(TABLE_EFFORT)
- ->where('objectID')->eq($effort->objectID)
- ->andWhere('objectType')->eq('task')
- ->andWhere('deleted')->eq('0')
- ->orderBy('date desc,id desc')->limit(2)->fetchAll();
- $lastTwoEfforts = isset($lastTwoEfforts[1]) ? $lastTwoEfforts[1] : '';
- if($lastTwoEfforts) $left = $lastTwoEfforts->left;
- if(empty($lastTwoEfforts) && $left == 0) $left = $task->estimate;
- }
- /* 如果该任务是多人团队任务则做一些额外的处理。*/
- if(empty($task->team)) return (float)$left;
- /* 获取要删除的工时的团队,如果要删除的工时的用户不是团队成员则不做任何处理。*/
- $currentTeam = $this->getTeamByAccount($task->team, $effort->account, array('effortID' => $effort->id, 'order' => $effort->order));
- if(!$currentTeam) return $left;
- $left = $currentTeam->left;
- if($task->mode == 'multi') /* 如果任务是多人并行任务。注:多人串行任务的mode是linear。*/
- {
- /* 获取要删除的工时对应的用户的工时信息列表。*/
- $accountEfforts = $this->getTaskEfforts($currentTeam->task, $effort->account, $effort->id);
- $lastEffort = array_pop($accountEfforts);
- if($lastEffort->id == $effort->id)
- {
- $lastTwoEfforts = array_pop($accountEfforts);
- if($lastTwoEfforts) $left = $lastTwoEfforts->left;
- }
- }
- /* 更新要删除的工时对应的用户的任务信息。*/
- $newTeamInfo = new stdclass();
- $newTeamInfo->consumed = $currentTeam->consumed - $effort->consumed;
- if($currentTeam->status != 'done') $newTeamInfo->left = $left;
- if($currentTeam->status == 'done' && $left > 0 && $task->mode == 'multi')
- {
- $newTeamInfo->status = 'doing';
- $newTeamInfo->left = $left;
- }
- if($currentTeam->status != 'done' && $newTeamInfo->consumed > 0 && $left == 0) $newTeamInfo->status = 'done';
- if($task->mode == 'multi' && $currentTeam->status == 'done' && ($newTeamInfo->consumed == 0 && $left == 0))
- {
- $newTeamInfo->status = 'doing';
- $newTeamInfo->left = $currentTeam->estimate;
- }
- $this->dao->update(TABLE_TASKTEAM)->data($newTeamInfo)->where('id')->eq($currentTeam->id)->exec();
- return (float)$left;
- }
- /**
- * 根据报表条件查询任务.
- * Get task list by report.
- *
- * @param string $field
- * @param string $condition
- * @access protected
- * @return object[]
- */
- protected function getListByReportCondition($field, $condition)
- {
- return $this->dao->select("id,{$field}")->from(TABLE_TASK)->where($condition)->fetchAll('id');
- }
- /**
- * 查询当前任务作为父任务时的状态。
- * Get the status of the taskID as a parent task.
- *
- * @param int $taskID
- * @access protected
- * @return string
- */
- protected function getParentStatusById($taskID)
- {
- $childrenStatus = $this->dao->select('id,status,parent')->from(TABLE_TASK)->where('parent')->eq($taskID)->andWhere('deleted')->eq('0')->fetchPairs('status', 'status');
- if(empty($childrenStatus)) return '';
- unset($childrenStatus['closed']);
- if(count($childrenStatus) == 0) return 'closed';
- unset($childrenStatus['cancel']);
- if(count($childrenStatus) == 0) return 'cancel';
- if(count($childrenStatus) == 1) return current($childrenStatus);
- if(isset($childrenStatus['doing']) || isset($childrenStatus['pause']) || isset($childrenStatus['wait'])) return 'doing';
- if(isset($childrenStatus['done'])) return 'done';
- return '';
- }
- /**
- * 如果任务是从Bug转来的,并且已经完成了,则获取提醒bug的链接。
- * Get a link to locate the bug if the task was transferred from the bug and it has already been finished.
- *
- * @param object $task
- * @param array $changes
- * @access protected
- * @return array
- */
- protected function getRemindBugLink($task, $changes)
- {
- foreach($changes as $change)
- {
- if($change['field'] == 'status' && $change['new'] == 'done')
- {
- $confirmURL = helper::createLink('bug', 'view', "id={$task->fromBug}");
- return array('result' => 'success', 'load' => true, 'callback' => "zui.Modal.confirm('" . sprintf($this->lang->task->remindBug, $task->fromBug) . "').then((res) => {if(res) loadModal('{$confirmURL}')});", 'closeModal' => true);
- }
- }
- return array();
- }
- /**
- * 获取edit方法的必填项。
- * Get required fields for edit method.
- *
- * @param object $task
- * @access protected
- * @return string
- */
- protected function getRequiredFields4Edit($task)
- {
- $execution = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($task->execution)->fetch();
- $requiredFields = ',' . $this->config->task->edit->requiredFields . ',';
- if($this->isNoStoryExecution($execution)) $requiredFields = str_replace(',story,', ',', $requiredFields);
- if(strpos(',doing,pause,', $task->status) && empty($task->left))
- {
- dao::$errors['left'] = sprintf($this->lang->task->error->leftEmptyAB, $this->lang->task->statusList[$task->status]);
- return false;
- }
- return trim($requiredFields, ',');
- }
- /**
- * 获取团队成员以及他的预计、消耗、剩余工时。
- * Get team account,estimate,consumed and left info.
- *
- * @param array $teamList
- * @param array $teamSourceList
- * @param array $teamEstimateList
- * @param array $teamConsumedList
- * @param array $teamLeftList
- * @access protected
- * @return object[]
- */
- protected function getTeamInfoList($teamList, $teamSourceList, $teamEstimateList, $teamConsumedList, $teamLeftList)
- {
- $teamInfoList = array();
- foreach($teamList as $index => $account)
- {
- if(empty($account)) continue;
- $teamInfo = new stdclass();
- $teamInfo->account = $account;
- $teamInfo->source = $teamSourceList[$index];
- $teamInfo->estimate = $teamEstimateList[$index];
- $teamInfo->consumed = $teamConsumedList[$index];
- $teamInfo->left = $teamLeftList[$index];
- $teamInfoList[$index] = $teamInfo;
- }
- return $teamInfoList;
- }
- /**
- * 维护团队成员信息。
- * Maintain team member information.
- *
- * @param object $member
- * @param string $mode multi|linear
- * @param bool $inTeam
- * @access protected
- * @return bool
- */
- protected function setTeamMember($member, $mode, $inTeam)
- {
- if($mode == 'multi' && $inTeam)
- {
- $this->dao->update(TABLE_TASKTEAM)
- ->beginIF($member->estimate)->set("estimate= estimate + {$member->estimate}")->fi()
- ->beginIF($member->left)->set("`left` = `left` + {$member->left}")->fi()
- ->beginIF($member->consumed)->set("`consumed` = `consumed` + {$member->consumed}")->fi()
- ->where('task')->eq($member->task)
- ->andWhere('account')->eq($member->account)
- ->exec();
- }
- else
- {
- $this->dao->insert(TABLE_TASKTEAM)->data($member)->autoCheck()->exec();
- }
- return !dao::isError();
- }
- /**
- * 通过任务ID列表查询任务团队信息。
- * Get task team by id list.
- *
- * @param array $taskIdList
- * @access protected
- * @return array[]
- */
- protected function getTeamMembersByIdList($taskIdList)
- {
- return $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in($taskIdList)->fetchGroup('task');
- }
- /**
- * Get a new task after deleting a workhour of task.
- *
- * @param object $effort
- * @param object $task
- * @return object
- */
- protected function getTaskAfterDeleteWorkhour($effort, $task)
- {
- /* Compute the left and consumed workhour of the task. */
- $consumed = $task->consumed - $effort->consumed;
- $left = $this->getLeftAfterDeleteWorkhour($effort, $task);
- /* Define and prepare one new task object to update the task. */
- $data = new stdclass();
- $data->consumed = $consumed;
- $data->left = $left;
- $data->status = ($left == 0 && $consumed != 0) ? 'done' : $task->status;
- if($effort->isLast && $consumed == 0 && $task->status != 'wait')
- {
- $data->status = 'wait';
- $data->left = $task->estimate;
- $data->finishedBy = '';
- $data->canceledBy = '';
- $data->closedBy = '';
- $data->closedReason = '';
- $data->finishedDate = null;
- $data->canceledDate = null;
- $data->closedDate = null;
- if($task->assignedTo == 'closed') $data->assignedTo = $this->app->user->account;
- }
- elseif($effort->isLast && $left != 0 && strpos('done,pause,cancel,closed', $task->status) !== false)
- {
- $data->status = 'doing';
- $data->finishedBy = '';
- $data->canceledBy = '';
- $data->closedBy = '';
- $data->closedReason = '';
- $data->finishedDate = null;
- $data->canceledDate = null;
- $data->closedDate = null;
- }
- elseif($consumed != 0 && $left == 0 && strpos('done,pause,cancel,closed', $task->status) === false)
- {
- $now = helper::now();
- $data->status = 'done';
- $data->assignedTo = $task->openedBy;
- $data->assignedDate = $now;
- $data->finishedBy = $this->app->user->account;
- $data->finishedDate = $now;
- }
- else
- {
- $data->status = $task->status;
- }
- return $data;
- }
- /**
- * 记录任务的版本。
- * Record task version.
- *
- * @param object $task
- * @access protected
- * @return bool
- */
- protected function recordTaskVersion($task)
- {
- $taskSpec = new stdclass();
- $taskSpec->task = $task->id;
- $taskSpec->version = $task->version;
- $taskSpec->name = $task->name;
- $taskSpec->estStarted = $task->estStarted;
- $taskSpec->deadline = $task->deadline;
- $this->dao->insert(TABLE_TASKSPEC)->data($taskSpec)->autoCheck()->exec();
- return !dao::isError();
- }
- /**
- * 将日志表中剩余设置为0。
- * Set effort left to 0.
- *
- * @param int $taskID
- * @param array $members
- * @access protected
- * @return bool
- */
- protected function resetEffortLeft($taskID, $members)
- {
- foreach($members as $account)
- {
- $this->dao->update(TABLE_EFFORT)->set('`left`')->eq(0)->where('account')->eq($account)
- ->andWhere('objectID')->eq($taskID)
- ->andWhere('objectType')->eq('task')
- ->orderBy('date_desc,id_desc')
- ->limit('1')
- ->exec();
- }
- return !dao::isError();
- }
- /**
- * 根据子任务以及父任务的状态更新父任务。
- * Update parent task status by child and parent status.
- *
- * @param object $task
- * @param object $childTask
- * @param string $status
- * @access protected
- * @return void
- */
- protected function autoUpdateTaskByStatus($task, $childTask, $status)
- {
- $now = helper::now();
- $account = $this->app->user->account;
- $data = new stdclass();
- $data->status = $status;
- if($status == 'done')
- {
- $data->assignedTo = $task->openedBy;
- $data->assignedDate = $now;
- $data->finishedBy = $account;
- $data->finishedDate = $now;
- $data->canceledBy = '';
- $data->canceledDate = null;
- }
- if($status == 'cancel')
- {
- $data->assignedTo = $task->openedBy;
- $data->assignedDate = $now;
- $data->finishedBy = '';
- $data->finishedDate = null;
- $data->canceledBy = $account;
- $data->canceledDate = $now;
- }
- if($status == 'closed')
- {
- $data->assignedTo = 'closed';
- $data->assignedDate = $now;
- $data->closedBy = $account;
- $data->closedDate = $now;
- $data->closedReason = $task->status == 'cancel' ? 'cancel' : 'done';
- }
- if($status == 'doing' || $status == 'wait')
- {
- if($task->assignedTo == 'closed')
- {
- $data->assignedTo = !empty($childTask) ? $childTask->assignedTo : $task->openedBy;
- $data->assignedDate = $now;
- }
- if($status == 'doing' && !empty($childTask->realStarted)) $data->realStarted = $childTask->realStarted;
- $data->finishedBy = '';
- $data->finishedDate = null;
- $data->canceledBy = '';
- $data->canceledDate = null;
- $data->closedBy = '';
- $data->closedDate = null;
- $data->closedReason = '';
- }
- $data->lastEditedBy = $account;
- $data->lastEditedDate = $now;
- $this->dao->update(TABLE_TASK)->data($data)->where('id')->eq($task->id)->exec();
- }
- /**
- * 通过拖动甘特图修改任务的预计开始日期和截止日期。
- * Update task estimate date and deadline through gantt.
- *
- * @param object $postData
- * @access protected
- * @return bool
- */
- protected function updateTaskEsDateByGantt($postData)
- {
- $task = $this->dao->select('*')->from(TABLE_TASK)->where('id')->eq($postData->id)->fetch();
- $isChildTask = $task->parent > 0;
- if($isChildTask) $parentTask = $this->dao->select('*')->from(TABLE_TASK)->where('id')->eq($task->parent)->fetch();
- $stage = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($task->execution)->andWhere('project')->eq($task->project)->fetch();
- $start = $isChildTask ? $parentTask->estStarted : $stage->begin;
- $end = $isChildTask ? $parentTask->deadline : $stage->end;
- $typeLang = $isChildTask ? $this->lang->task->parent : $this->lang->project->stage;
- if(helper::diffDate($start, $postData->startDate) > 0) dao::$errors[] = sprintf($this->lang->task->overEsStartDate, $typeLang, $typeLang);
- if(helper::diffDate($end, $postData->endDate) < 0) dao::$errors[] = sprintf($this->lang->task->overEsEndDate, $typeLang, $typeLang);
- if(dao::isError()) return false;
- /* Update estimate started and deadline of a task. */
- $this->dao->update(TABLE_TASK)
- ->set('estStarted')->eq($postData->startDate)
- ->set('deadline')->eq($postData->endDate)
- ->set('lastEditedBy')->eq($this->app->user->account)
- ->where('id')->eq($postData->id)
- ->exec();
- return !dao::isError();
- }
- /**
- * 通过填写的日志更新多人任务的团队表,计算多人任务的工时。
- * Update team of multi-task by effort.
- *
- * @param int $effortID
- * @param object $record
- * @param object $currentTeam
- * @param object $task
- * @access protected
- * @return void
- * @param string $lastDate
- */
- protected function updateTeamByEffort($effortID, $record, $currentTeam, $task, $lastDate)
- {
- $this->dao->update(TABLE_TASKTEAM)
- ->set("consumed = consumed + {$record->consumed}")
- ->set('status')->eq($currentTeam->status)
- ->beginIF($record->date >= $lastDate)->set('left')->eq($record->left)->fi()
- ->where('id')->eq($currentTeam->id)
- ->exec();
- if($task->mode == 'linear' && empty($record->order)) $this->updateEffortOrder($effortID, $currentTeam->order);
- }
- /**
- * 更新父子任务关系。
- * Update relation of parent and child.
- *
- * @param int $childID
- * @param int $parentID
- * @access protected
- * @return void
- */
- protected function updateRelation($childID, $parentID = 0)
- {
- if(empty($childID)) return;
- $relation = $this->dao->select('*')->from(TABLE_RELATION)->where('AID')->eq($childID)->andWhere('relation')->eq('subdividefrom')->andWhere('AType')->eq('task')->andWhere('BType')->eq('task')->fetch();
- if($relation)
- {
- if($parentID && $relation->BID == $parentID) return;
- $this->dao->delete()->from(TABLE_RELATION)->where('BID')->eq($childID)->andWhere('relation')->eq('subdivideinto')->andWhere('AType')->eq('task')->andWhere('BType')->eq('task')->exec();
- $this->dao->delete()->from(TABLE_RELATION)->where('AID')->eq($childID)->andWhere('relation')->eq('subdividefrom')->andWhere('AType')->eq('task')->andWhere('BType')->eq('task')->exec();
- }
- if(empty($parentID)) return;
- $data = new stdclass();
- $data->AType = 'task';
- $data->BType = 'task';
- $data->AID = $childID;
- $data->BID = $parentID;
- $data->relation = 'subdividefrom';
- $this->dao->insert(TABLE_RELATION)->data($data)->exec();
- $data->AID = $parentID;
- $data->BID = $childID;
- $data->relation = 'subdivideinto';
- $this->dao->insert(TABLE_RELATION)->data($data)->exec();
- return;
- }
- /**
- * 归并子任务到父任务下。
- * Merge child into parent
- *
- * @param array $tasks
- * @access public
- * @return array
- */
- public function mergeChildIntoParent($tasks)
- {
- $mergeTasks = function($parents, $parentID = 0) use(&$mergeTasks)
- {
- $tasks = array();
- if(!isset($parents[$parentID])) return $tasks;
- foreach($parents[$parentID] as $childTask)
- {
- $tasks[$childTask->id] = $childTask;
- if(isset($parents[$childTask->id])) $tasks += $mergeTasks($parents, $childTask->id);
- }
- return $tasks;
- };
- $parents = array();
- foreach($tasks as $task) $parents[$task->parent][$task->id] = $task;
- $mergedTasks = array();
- foreach($tasks as $task)
- {
- if(isset($mergedTasks[$task->id])) continue;
- if($task->parent && isset($tasks[$task->parent])) continue;
- $mergedTasks[$task->id] = $task;
- $mergedTasks += $mergeTasks($parents, $task->id);
- }
- return $mergedTasks;
- }
- }
|