From 4e230ca629f4a4d349ff8e4380630a4a1d60b87b Mon Sep 17 00:00:00 2001 From: tianshujie Date: Fri, 26 May 2023 09:58:55 +0800 Subject: [PATCH] * Refactor batchedit method and add test case for task module. --- module/task/config/form.php | 70 ++-- module/task/control.php | 23 +- module/task/lang/de.php | 2 +- module/task/lang/en.php | 2 +- module/task/lang/fr.php | 2 +- module/task/lang/zh-cn.php | 2 +- module/task/model.php | 355 +++++------------- module/task/tao.php | 13 - module/task/test/model/activate.php | 1 + module/task/test/model/afterbatchupdate.php | 54 +++ module/task/test/model/assign.php | 3 +- module/task/test/model/batchupdate.php | 67 +++- .../{tao => model}/getteammembersbyidlist.php | 0 .../model/yaml/afterbatchupdate/project.yaml | 19 + .../model/yaml/afterbatchupdate/task.yaml | 46 +++ .../test/model/yaml/batchupdate/project.yaml | 19 + .../test/model/yaml/batchupdate/task.yaml | 42 +++ .../test/model/yaml/batchupdate/taskspec.yaml | 6 + module/task/test/task.class.php | 82 ++-- module/task/view/batchedit.html.php | 58 +-- module/task/zen.php | 299 ++++++++++----- 21 files changed, 667 insertions(+), 498 deletions(-) create mode 100644 module/task/test/model/afterbatchupdate.php rename module/task/test/{tao => model}/getteammembersbyidlist.php (100%) create mode 100755 module/task/test/model/yaml/afterbatchupdate/project.yaml create mode 100755 module/task/test/model/yaml/afterbatchupdate/task.yaml create mode 100755 module/task/test/model/yaml/batchupdate/project.yaml create mode 100755 module/task/test/model/yaml/batchupdate/task.yaml create mode 100644 module/task/test/model/yaml/batchupdate/taskspec.yaml diff --git a/module/task/config/form.php b/module/task/config/form.php index 1aed6e5b89..657af7c83a 100644 --- a/module/task/config/form.php +++ b/module/task/config/form.php @@ -87,24 +87,26 @@ $config->task->form->team->create['teamLeft'] = array('type' => 'array', 're $config->task->form->team->edit = $config->task->form->team->create; $config->task->form->team->edit['deleteFiles'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit = array(); -$config->task->form->batchEdit['taskIDList'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['modules'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['names'] = array('type' => 'array', 'required' => true, 'default' => array()); -$config->task->form->batchEdit['colors'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['types'] = array('type' => 'array', 'required' => true, 'default' => array()); -$config->task->form->batchEdit['statuses'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['assignedTos'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['estimates'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['estStarteds'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['consumeds'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['lefts'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['finishedBys'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['canceledBys'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['closedBys'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['closedReasons'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['deadlines'] = array('type' => 'array', 'required' => false, 'default' => array()); -$config->task->form->batchEdit['pris'] = array('type' => 'array', 'required' => false, 'default' => array()); +$config->task->form->batchcreate = common::formConfig('task', 'batchEdit'); +$config->task->form->batchedit['taskIDList'] = array('type' => 'int', 'required' => false, 'default' => 0, 'base' => true); +$config->task->form->batchedit['module'] = array('type' => 'int', 'required' => false, 'default' => 0); +$config->task->form->batchedit['name'] = array('type' => 'string', 'required' => true, 'default' => ''); +$config->task->form->batchedit['color'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->batchedit['type'] = array('type' => 'string', 'required' => true, 'default' => ''); +$config->task->form->batchedit['status'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->batchedit['pri'] = array('type' => 'int', 'required' => false, 'default' => 0); +$config->task->form->batchedit['assignedTo'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->batchedit['estimate'] = array('type' => 'float', 'required' => false, 'default' => 0); +$config->task->form->batchedit['consumed'] = array('type' => 'float', 'required' => false, 'default' => 0); +$config->task->form->batchedit['left'] = array('type' => 'float', 'required' => false, 'default' => 0); +$config->task->form->batchedit['finishedBy'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->batchedit['canceledBy'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->batchedit['closedBy'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->batchedit['closedReason'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->batchedit['estStarted'] = array('type' => 'date', 'required' => false, 'default' => null); +$config->task->form->batchedit['deadline'] = array('type' => 'date', 'required' => false, 'default' => null); +$config->task->form->batchedit['lastEditedBy'] = array('type' => 'string', 'required' => false, 'default' => $app->user->account); +$config->task->form->batchedit['lastEditedDate'] = array('type' => 'datetime', 'required' => false, 'default' => $now); $config->task->form->batchcreate = common::formConfig('task', 'batchCreate'); $config->task->form->batchcreate['module'] = array('type' => 'int', 'required' => false, 'default' => 0); @@ -130,22 +132,22 @@ $config->task->form->pause['lastEditedDate'] = array('type' => 'string', 'requir $config->task->form->pause['status'] = array('type' => 'string', 'required' => false, 'default' => 'pause'); $config->task->form->activate = array(); -$config->task->form->activate['mode'] = array('type' => 'string', 'required' => false, 'default' => ''); -$config->task->form->activate['left'] = array('type' => 'float', 'required' => true, 'default' => 0); -$config->task->form->activate['assignedTo'] = array('type' => 'string', 'required' => false); -$config->task->form->activate['comment'] = array('type' => 'string', 'required' => false, 'control' => 'editor'); -$config->task->form->activate['status'] = array('type' => 'string', 'required' => false, 'default' => 'doing'); -$config->task->form->activate['activatedDate'] = array('type' => 'string', 'required' => false, 'default' => $now); -$config->task->form->activate['assignedDate'] = array('type' => 'string', 'required' => false, 'default' => $now); -$config->task->form->activate['lastEditedBy'] = array('type' => 'string', 'required' => false, 'default' => $app->user->account); -$config->task->form->activate['lastEditedDate'] = array('type' => 'string', 'required' => false, 'default' => $now); -$config->task->form->activate['finishedBy'] = array('type' => 'string', 'required' => false, 'default' => ''); -$config->task->form->activate['canceledBy'] = array('type' => 'string', 'required' => false, 'default' => ''); -$config->task->form->activate['closedBy'] = array('type' => 'string', 'required' => false, 'default' => ''); -$config->task->form->activate['closedReason'] = array('type' => 'string', 'required' => false, 'default' => ''); -$config->task->form->activate['finishedDate'] = array('type' => 'string', 'required' => false, 'default' => null); -$config->task->form->activate['canceledDate'] = array('type' => 'string', 'required' => false, 'default' => null); -$config->task->form->activate['closedDate'] = array('type' => 'string', 'required' => false, 'default' => null); +$config->task->form->activate['mode'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->activate['left'] = array('type' => 'float', 'required' => true); +$config->task->form->activate['assignedTo'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->activate['comment'] = array('type' => 'string', 'required' => false, 'default' => '', 'control' => 'editor'); +$config->task->form->activate['status'] = array('type' => 'string', 'required' => false, 'default' => 'doing'); +$config->task->form->activate['activatedDate'] = array('type' => 'string', 'required' => false, 'default' => $now); +$config->task->form->activate['assignedDate'] = array('type' => 'string', 'required' => false, 'default' => $now); +$config->task->form->activate['lastEditedBy'] = array('type' => 'string', 'required' => false, 'default' => $app->user->account); +$config->task->form->activate['lastEditedDate'] = array('type' => 'string', 'required' => false, 'default' => $now); +$config->task->form->activate['finishedBy'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->activate['canceledBy'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->activate['closedBy'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->activate['closedReason'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->activate['finishedDate'] = array('type' => 'datetime', 'required' => false, 'default' => null); +$config->task->form->activate['canceledDate'] = array('type' => 'datetime', 'required' => false, 'default' => null); +$config->task->form->activate['closedDate'] = array('type' => 'datetime', 'required' => false, 'default' => null); $config->task->form->start = array(); $config->task->form->start['status'] = array('type' => 'string', 'required' => false, 'default' => 'doing'); diff --git a/module/task/control.php b/module/task/control.php index 4d171b8eea..b9420c8141 100755 --- a/module/task/control.php +++ b/module/task/control.php @@ -192,7 +192,7 @@ class task extends control /** * 批量编辑任务。 - * Batch edit task. + * Batch edit tasks. * * @param int $executionID * @access public @@ -200,11 +200,16 @@ class task extends control */ public function batchEdit(int $executionID = 0) { - if($this->post->names) + if($this->post->name) { /* Batch edit tasks. */ - $postData = form::data($this->config->task->form->batchEdit)->get(); - $allChanges = $this->task->batchUpdate($postData); + $taskData = $this->taskZen->buildTasksForBatchEdit(); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + $allChanges = $this->task->batchUpdate($taskData); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + $this->task->afterBatchUpdate($taskData); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); $response = $this->taskZen->responseAfterBatchEdit($allChanges); @@ -212,12 +217,8 @@ class task extends control } if(!$this->post->taskIDList) $this->locate($this->session->taskList); - $taskIdList = array_unique($this->post->taskIDList); - /* Set parameters based on whether the page is execution or my. */ - $this->taskZen->batchEdit4Pages($executionID); - - $this->taskZen->buildBatchEditForm($taskIdList, $executionID); + $this->taskZen->assignBatchEditVars($executionID); } /** @@ -958,7 +959,9 @@ class task extends control if(!empty($_POST)) { /* Prepare the data information before activate the task. */ - $task = $this->taskZen->buildTaskForActivate($taskID); + $task = $this->taskZen->buildTaskForActivate($taskID); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + $teamData = form::data($this->config->task->form->team->edit)->get(); $changes = $this->task->activate($task, $this->post->comment, $teamData, $output); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); diff --git a/module/task/lang/de.php b/module/task/lang/de.php index 3dfc32d58c..0f66f32dfa 100644 --- a/module/task/lang/de.php +++ b/module/task/lang/de.php @@ -270,7 +270,7 @@ $lang->task->error->totalNumber = '"Total Cost" must be numbers.'; $lang->task->error->consumedNumber = '"Verbraucht" muss eine Zahl sein.'; $lang->task->error->estimateNumber = '"Schätzungen" müssen immer eine positive Zahl sein.'; $lang->task->error->leftNumber = '"Left" must be numbers.'; -$lang->task->error->recordMinus = 'Work hours should not be negative number.'; +$lang->task->error->recordMinus = '%s should not be negative number.'; $lang->task->error->consumedSmall = '"Genutzt" muss larger than before.'; $lang->task->error->dateEmpty = 'Please enter "Date"'; $lang->task->error->consumedThisTime = 'Bitte geben Sie die Stunden an'; diff --git a/module/task/lang/en.php b/module/task/lang/en.php index f9c3263ac6..6c200ae8d9 100755 --- a/module/task/lang/en.php +++ b/module/task/lang/en.php @@ -270,7 +270,7 @@ $lang->task->error->totalNumber = '"Total Cost" must be numbers.'; $lang->task->error->consumedNumber = '"Current Cost" must be numbers.'; $lang->task->error->estimateNumber = '"Estimates" must be a pasitive number.'; $lang->task->error->leftNumber = '"Left" must be numbers.'; -$lang->task->error->recordMinus = 'Work hours should not be negative number.'; +$lang->task->error->recordMinus = '%s should not be negative number.'; $lang->task->error->consumedSmall = '"Total Cost" must be > the last number.'; $lang->task->error->dateEmpty = 'Please enter "Date"'; $lang->task->error->consumedThisTime = 'Please enter "Hours Cost"'; diff --git a/module/task/lang/fr.php b/module/task/lang/fr.php index 44483742e6..fef0b5a413 100644 --- a/module/task/lang/fr.php +++ b/module/task/lang/fr.php @@ -270,7 +270,7 @@ $lang->task->error->totalNumber = '"Coût Total" doit être numérique.'; $lang->task->error->consumedNumber = '"Coût" doit être numérique.'; $lang->task->error->estimateNumber = '"Les estimations" doivent toujours être un nombre positif.'; $lang->task->error->leftNumber = '"Entrez" doit être numérique.'; -$lang->task->error->recordMinus = 'Work hours should not be negative number.'; +$lang->task->error->recordMinus = '%s should not be negative number.'; $lang->task->error->consumedSmall = '"Coût Total" doit être > au dernier chiffre.'; $lang->task->error->dateEmpty = 'Please enter "Date"'; $lang->task->error->consumedThisTime = 'Entrez le "Coût en Heures"'; diff --git a/module/task/lang/zh-cn.php b/module/task/lang/zh-cn.php index 884386e173..78d119fef3 100755 --- a/module/task/lang/zh-cn.php +++ b/module/task/lang/zh-cn.php @@ -270,7 +270,7 @@ $lang->task->error->totalNumber = '"总计消耗"必须为数字'; $lang->task->error->consumedNumber = '"本次消耗"必须为数字'; $lang->task->error->estimateNumber = '"最初预计"必须为正数'; $lang->task->error->leftNumber = '"预计剩余"必须为数字'; -$lang->task->error->recordMinus = '工时不能为负数'; +$lang->task->error->recordMinus = '%s不能为负数'; $lang->task->error->consumedSmall = '"总计消耗"必须大于之前消耗'; $lang->task->error->dateEmpty = '请填写"日期"'; $lang->task->error->consumedThisTime = '请填写"消耗"'; diff --git a/module/task/model.php b/module/task/model.php index 1ce6fea440..9e0cf47956 100755 --- a/module/task/model.php +++ b/module/task/model.php @@ -182,7 +182,7 @@ class taskModel extends model /** * 批量创建任务后的其他数据处理。 - * Other data process after task batch create. + * other data process after task batch create. * * @param array $taskIdList * @param int $parentID @@ -211,6 +211,57 @@ class taskModel extends model return !dao::isError(); } + /** + * 批量编辑任务后的其他数据处理。 + * other data process after task batch edit. + * + * @param object[] $tasks + * @access public + * @return bool + */ + public function afterBatchUpdate(array $tasks): bool + { + $this->loadModel('story'); + $this->loadModel('kanban'); + $this->loadModel('feedback'); + + $today = helper::today(); + $currentAccount = $this->app->user->account; + $oldTasks = $tasks ? $this->getByList(array_keys($tasks)) : array(); + foreach($tasks as $taskID => $task) + { + $oldTask = zget($oldTasks, $taskID); + + /* Record effort. */ + $record = new stdclass(); + $record->account = $currentAccount; + $record->task = $taskID; + $record->date = $today; + $record->left = $task->left; + $record->consumed = $task->consumed; + $this->addTaskEffort($record); + + /* Update Kanban and story stage. */ + if($oldTask->story) $this->story->setStage($oldTask->story); + if($task->status != $oldTask->status) $this->kanban->updateLane($oldTask->execution, 'task', $taskID); + + /* Udate parent task's status, date and hour. */ + if($oldTask->parent > 0) + { + $this->updateParentStatus($taskID); + $this->computeBeginAndEnd($oldTask->parent); + } + + if($this->config->edition != 'open' && $oldTask->feedback && !isset($feedbacks[$oldTask->feedback])) + { + $feedbacks[$oldTask->feedback] = $oldTask->feedback; + $this->feedback->updateStatus('task', $oldTask->feedback, $task->status, $oldTask->status); + } + } + + return !dao::isError(); + } + /** * Create task from gitlab issue. * @@ -803,283 +854,56 @@ class taskModel extends model * 批量更新任务。 * Batch update tasks. * - * @param object $postData + * @param array $taskData * @access public * @return array[]|false */ - public function batchUpdate(object $postData): array|false + public function batchUpdate(array $taskData): array|false { - $tasks = array(); + $this->loadModel('action'); + $this->loadModel('score'); + $allChanges = array(); - $now = helper::now(); - $today = date(DT_DATE1); - $taskIdList = $postData->taskIDList; - - /* Process data if the value is 'ditto'. */ - foreach($taskIdList as $taskID) + $oldTasks = $taskData ? $this->getByList(array_keys($taskData)) : array(); + foreach($taskData as $taskID => $task) { - if(isset($postData->modules[$taskID]) and ($postData->modules[$taskID] == 'ditto')) $postData->modules[$taskID] = isset($prev['module']) ? $prev['module'] : 0; - if($postData->types[$taskID] == 'ditto') $postData->types[$taskID] = isset($prev['type']) ? $prev['type'] : ''; - if($postData->pris[$taskID] == 'ditto') $postData->pris[$taskID] = isset($prev['pri']) ? $prev['pri'] : 0; - if($postData->finishedBys[$taskID] == 'ditto') $postData->finishedBys[$taskID] = isset($prev['finishedBy']) ? $prev['finishedBy'] : ''; - if($postData->canceledBys[$taskID] == 'ditto') $postData->canceledBys[$taskID] = isset($prev['canceledBy']) ? $prev['canceledBy'] : ''; - if($postData->closedBys[$taskID] == 'ditto') $postData->closedBys[$taskID] = isset($prev['closedBy']) ? $prev['closedBy'] : ''; - if($postData->estStarteds[$taskID] == '0000-00-00') $postData->estStarteds[$taskID] = ''; - if($postData->deadlines[$taskID] == '0000-00-00') $postData->deadlines[$taskID] = ''; - if(isset($postData->assignedTos[$taskID]) and $postData->assignedTos[$taskID] == 'ditto') $postData->assignedTos[$taskID] = isset($prev['assignedTo']) ? $prev['assignedTo'] : ''; - - $prev['module'] = $postData->modules[$taskID]; - $prev['type'] = $postData->types[$taskID]; - $prev['pri'] = $postData->pris[$taskID]; - $prev['finishedBy'] = $postData->finishedBys[$taskID]; - $prev['canceledBy'] = $postData->canceledBys[$taskID]; - $prev['closedBy'] = $postData->closedBys[$taskID]; - if(isset($postData->assignedTos[$taskID])) $prev['assignedTo'] = $postData->assignedTos[$taskID]; - } - - /* Initialize tasks from the post data.*/ - $extendFields = $this->getFlowExtendFields(); - $oldTasks = $taskIdList ? $this->getByList($taskIdList) : array(); - $tasks = array(); - foreach($taskIdList as $taskID) - { - $oldTask = $oldTasks[$taskID]; - - $task = new stdclass(); - $task->id = $taskID; - $task->color = $postData->colors[$taskID]; - $task->name = $postData->names[$taskID]; - $task->module = isset($postData->modules[$taskID]) ? $postData->modules[$taskID] : 0; - $task->type = $postData->types[$taskID]; - $task->status = isset($postData->statuses[$taskID]) ? $postData->statuses[$taskID] : $oldTask->status; - $task->pri = $postData->pris[$taskID]; - $task->estimate = isset($postData->estimates[$taskID]) ? $postData->estimates[$taskID] : $oldTask->estimate; - $task->left = isset($postData->lefts[$taskID]) ? $postData->lefts[$taskID] : $oldTask->left; - $task->estStarted = $postData->estStarteds[$taskID]; - $task->deadline = $postData->deadlines[$taskID]; - $task->finishedBy = $postData->finishedBys[$taskID]; - $task->canceledBy = $postData->canceledBys[$taskID]; - $task->closedBy = $postData->closedBys[$taskID]; - $task->closedReason = $postData->closedReasons[$taskID]; - $task->finishedDate = $oldTask->finishedBy == $task->finishedBy ? $oldTask->finishedDate : $now; - $task->canceledDate = $oldTask->canceledBy == $task->canceledBy ? $oldTask->canceledDate : $now; - $task->closedDate = $oldTask->closedBy == $task->closedBy ? $oldTask->closedDate : $now; - $task->lastEditedBy = $this->app->user->account; - $task->lastEditedDate = $now; - $task->consumed = $oldTask->consumed; - $task->parent = $oldTask->parent; - - if(isset($postData->assignedTos[$taskID])) $task->assignedTo = $postData->assignedTos[$taskID]; - if($task->status == 'closed') $task->assignedTo = 'closed'; - if(isset($task->assignedTo) and $oldTask->assignedTo != $task->assignedTo) $task->assignedDate = $now; - - if(strpos(',doing,pause,', $task->status) and empty($oldTask->mode) and empty($task->left) and $task->parent >= 0) - { - dao::$errors[] = sprintf($this->lang->task->error->leftEmptyAB, zget($this->lang->task->statusList, $task->status)); - return false; - } - - if(!empty($this->config->limitTaskDate)) - { - $this->checkEstStartedAndDeadline($oldTask->execution, $task->estStarted, $task->deadline, "task:{$taskID} "); - if(dao::isError()) return false; - } - - if(empty($task->closedReason) and $task->status == 'closed') - { - if($oldTask->status == 'done') $task->closedReason = 'done'; - if($oldTask->status == 'cancel') $task->closedReason = 'cancel'; - } - - if($oldTask->name != $task->name || $oldTask->estStarted != $task->estStarted || $oldTask->deadline != $task->deadline) - { - $task->version = $oldTask->version + 1; - } - - foreach($extendFields as $extendField) - { - $task->{$extendField->field} = $this->post->{$extendField->field}[$taskID]; - if(is_array($task->{$extendField->field})) $task->{$extendField->field} = implode(',', $task->{$extendField->field}); - - $task->{$extendField->field} = htmlSpecialString($task->{$extendField->field}); - } - - if(!empty($postData->consumeds[$taskID])) - { - if($postData->consumeds[$taskID] < 0) - { - dao::$errors[] = sprintf($this->lang->task->error->consumed, $taskID); - return false; - } - else - { - $record = new stdclass(); - $record->account = $this->app->user->account; - $record->task = $taskID; - $record->date = $today; - $record->left = $task->left; - $record->consumed = $postData->consumeds[$taskID]; - $this->addTaskEstimate($record); - - $task->consumed = $oldTask->consumed + $record->consumed; - } - } - - switch($task->status) - { - case 'done': - $task->left = 0; - if(!$task->finishedBy) $task->finishedBy = $this->app->user->account; - if($task->closedReason) $task->closedDate = $now; - $task->finishedDate = $oldTask->status == 'done' ? $oldTask->finishedDate : $now; - - $task->canceledBy = ''; - $task->canceledDate = ''; - break; - case 'cancel': - $task->assignedTo = $oldTask->openedBy; - $task->assignedDate = $now; - - if(!$task->canceledBy) - { - $task->canceledBy = $this->app->user->account; - $task->canceledDate = $now; - } - - $task->finishedBy = ''; - $task->finishedDate = ''; - break; - case 'closed': - if(!$task->closedBy) - { - $task->closedBy = $this->app->user->account; - $task->closedDate = $now; - } - if($task->closedReason == 'cancel' and helper::isZeroDate($task->finishedDate)) $task->finishedDate = ''; - break; - case 'wait': - if($task->consumed > 0 and $task->left > 0) $task->status = 'doing'; - if($task->left == $oldTask->left and $task->consumed == 0) $task->left = $task->estimate; - - $task->canceledDate = ''; - $task->finishedDate = ''; - $task->closedDate = ''; - break; - case 'doing': - $task->canceledDate = ''; - $task->finishedDate = ''; - $task->closedDate = ''; - break; - case 'pause': - $task->finishedDate = ''; - default: - break; - } - if($task->assignedTo) $task->assignedDate = $now; - - $tasks[$taskID] = $task; - } - - /* Check field not empty. */ - foreach($tasks as $taskID => $task) - { - if($task->status == 'cancel') continue; - if($task->status == 'done' and $task->consumed === false) - { - dao::$errors[] = 'Task#' . $taskID . sprintf($this->lang->error->notempty, $this->lang->task->consumedThisTime); - return false; - } - - if(!empty($task->deadline) and $task->estStarted > $task->deadline) - { - dao::$errors[] = 'Task#' . $taskID . $this->lang->task->error->deadlineSmall; - return false; - } - - foreach(explode(',', $this->config->task->edit->requiredFields) as $field) - { - $field = trim($field); - if(empty($field)) continue; - - if(!isset($task->$field)) continue; - if(!empty($task->$field)) continue; - if($field == 'estimate' and strlen(trim($task->estimate)) != 0) continue; - - dao::$errors['message'][] = sprintf($this->lang->error->notempty, $this->lang->task->$field); - return false; - } - } - - $isBiz = $this->config->edition == 'biz'; - $isMax = $this->config->edition == 'max'; - foreach($tasks as $taskID => $task) - { - if(strpos(',doing,pause,', $task->status) && empty($oldTask->mode) && $task->parent >= 0 && empty($task->left)) - { - dao::$errors[] = sprintf($this->lang->task->error->leftEmpty, $taskID, $this->lang->task->statusList[$task->status]); - return false; - } - - $oldTask = $oldTasks[$taskID]; + /* Update a task.*/ + $oldTask = zget($oldTasks, $taskID); $this->dao->update(TABLE_TASK)->data($task) ->autoCheck() - - ->checkIF($task->estimate !== false, 'estimate', 'float') - ->checkIF($task->consumed !== false, 'consumed', 'float') - ->checkIF($task->left !== false, 'left', 'float') - - ->batchCheckIF($task->status == 'wait' or $task->status == 'doing', 'finishedBy, finishedDate,canceledBy, canceledDate, closedBy, closedDate, closedReason', 'empty') - - ->checkIF($task->status == 'done', 'consumed', 'notempty') - ->checkIF($task->status == 'done' and $task->closedReason, 'closedReason', 'equal', 'done') - ->batchCheckIF($task->status == 'done', 'canceledBy, canceledDate', 'empty') - - ->batchCheckIF($task->closedReason == 'cancel', 'finishedBy, finishedDate', 'empty') + ->batchCheck($this->config->task->edit->requiredFields, 'notempty') ->checkFlow() - ->where('id')->eq((int)$taskID) + ->where('id')->eq($taskID) ->exec(); + if(dao::isError()) { - dao::$errors[] = 'Task#' . $taskID . dao::getError(true); + foreach(dao::getError() as $field => $error) dao::$errors["{$field}[{$taskID}]"] = $error; return false; } - if($task->status == 'done' and $task->closedReason) $this->dao->update(TABLE_TASK)->set('status')->eq('closed')->where('id')->eq($taskID)->exec(); - - if($oldTask->story !== false) $this->loadModel('story')->setStage($oldTask->story); - if(!dao::isError()) + /* Create the task description of the current version in the database. */ + if($task->version > $oldTask->version) { - /* Record version change history. */ - if($task->version > $oldTask->version) - { - $taskSpec = new stdclass(); - $taskSpec->task = $taskID; - $taskSpec->version = $task->version; - $taskSpec->name = $task->name; - $taskSpec->estStarted = $task->estStarted; - $taskSpec->deadline = $task->deadline; + $taskSpec = new stdclass(); + $taskSpec->task = $taskID; + $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(); - } - - if($oldTask->parent > 0) - { - $this->updateParentStatus($oldTask->id); - $this->computeBeginAndEnd($oldTask->parent); - } - - if($task->status == 'done') $this->loadModel('score')->create('task', 'finish', $taskID); - if($task->status == 'closed') $this->loadModel('score')->create('task', 'close', $taskID); - if($task->status != $oldTask->status) $this->loadModel('kanban')->updateLane($oldTask->execution, 'task', $oldTask->id); - if(($isBiz || $isMax) && $oldTask->feedback && !isset($feedbacks[$oldTask->feedback])) - { - $feedbacks[$oldTask->feedback] = $oldTask->feedback; - $this->loadModel('feedback')->updateStatus('task', $oldTask->feedback, $task->status, $oldTask->status); - } - $allChanges[$taskID] = common::createChanges($oldTask, $task); + $this->dao->insert(TABLE_TASKSPEC)->data($taskSpec)->autoCheck()->exec(); } + + if($task->status == 'done') $this->score->create('task', 'finish', $taskID); + if($task->status == 'closed') $this->score->create('task', 'close', $taskID); + $actionID = $this->action->create('task', $taskID, 'Edited'); + $changes = common::createChanges($oldTask, $task); + if(!empty($changes)) $this->action->logHistory($actionID, $changes); + + $allChanges[$taskID] = $changes; } - if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchEdit'); + if(!dao::isError()) $this->score->create('ajax', 'batchEdit'); return $allChanges; } @@ -2112,6 +1936,19 @@ class taskModel extends model return $estimate; } + /** + * 通过任务ID列表查询任务团队信息。 + * Get task team by id list. + * + * @param array $taskIdList + * @access public + * @return object[] + */ + public function getTeamMembersByIdList(array $taskIdList): array + { + return $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in($taskIdList)->fetchGroup('task'); + } + /** * Check operate effort. * diff --git a/module/task/tao.php b/module/task/tao.php index 111f546116..f502f6632c 100644 --- a/module/task/tao.php +++ b/module/task/tao.php @@ -471,19 +471,6 @@ class taskTao extends taskModel return trim($requiredFields, ','); } - /** - * 通过任务ID列表查询任务团队信息。 - * Get task team by id list. - * - * @param array $taskIdList - * @access protected - * @return object[] - */ - protected function getTeamMembersByIdList(array $taskIdList): array - { - return $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in($taskIdList)->fetchGroup('task'); - } - /** * 获取团队成员以及他的预计、消耗、剩余工时。 * Get team account,estimate,consumed and left info. diff --git a/module/task/test/model/activate.php b/module/task/test/model/activate.php index 4c88a8024d..253ac42ce0 100755 --- a/module/task/test/model/activate.php +++ b/module/task/test/model/activate.php @@ -2,6 +2,7 @@ config('project')->gen(6); zdTable('task')->config('task')->gen(9); diff --git a/module/task/test/model/afterbatchupdate.php b/module/task/test/model/afterbatchupdate.php new file mode 100644 index 0000000000..e2bb512508 --- /dev/null +++ b/module/task/test/model/afterbatchupdate.php @@ -0,0 +1,54 @@ +#!/usr/bin/env php +gen(5); +zdTable('product')->gen(5); +zdTable('story')->gen(5); +zdTable('project')->config('project')->gen(5); +zdTable('task')->config('task')->gen(15); + +/** + +title=测试taskModel->afterBatchUpdate(); +timeout=0 +cid=1 + +*/ + +$taskIdList = range(1, 15); + +$changeName = array('id' => 1, 'name' => '修改1'); +$changeModule = array('id' => 2, 'module' => 1); +$changeType = array('id' => 3, 'type' => 'devel'); +$changeAssignedTo = array('id' => 4, 'assignedTo' => 'admin'); +$changeStatus = array('id' => 5, 'status' => 'done'); +$changeEstStarted = array('id' => 6, 'estStarted' => '2023-05-25'); +$changeDeadline = array('id' => 7, 'estStarted' => '2023-01-02', 'deadline' => '2023-05-25'); +$changePri = array('id' => 8, 'pri' => 1); +$changeEstimate = array('id' => 9, 'estimate' => 1); +$changeConsumed = array('id' => 10, 'status' => 'doing', 'consumed' => 1); +$changeLeft = array('id' => 11, 'status' => 'doing', 'consumed' => 2, 'left' => 1); +$changeFinishedBy = array('id' => 12, 'status' => 'done', 'finishedBy' => 'admin'); +$changeClosedBy = array('id' => 13, 'status' => 'closed', 'closedBy' => 'admin', 'closedReason' => 'closed'); +$changeChildStatus = array('id' => 14, 'status' => 'doing'); +$changeChildConsumed = array('id' => 15, 'status' => 'doing', 'consumed' => 1); + +$taskTester = new taskTest(); +r($taskTester->afterBatchUpdateObject($taskIdList, $changeName)) && p() && e('1'); // 检查修改任务名称后的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changeModule)) && p() && e('1'); // 检查修改任务模块后的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changeType)) && p() && e('1'); // 检查修改任务类型后的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changeAssignedTo)) && p() && e('1'); // 检查修改任务指派人后的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changeStatus)) && p() && e('1'); // 检查修改任务状态后的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changeEstStarted)) && p() && e('1'); // 检查修改任务预计开始后的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changeDeadline)) && p() && e('1'); // 检查修改任务截止日期后的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changePri)) && p() && e('1'); // 检查修改任务优先级后的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changeEstimate)) && p() && e('1'); // 检查修改任务预计工时后的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changeConsumed)) && p() && e('1'); // 检查修改任务消耗工时后的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changeLeft)) && p() && e('1'); // 检查修改任务剩余工时后的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changeFinishedBy)) && p() && e('1'); // 检查修改任务完成者后的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changeClosedBy)) && p() && e('1'); // 检查修改任务关闭者后的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changeChildStatus)) && p() && e('1'); // 检查修改子任务状态的数据处理 +r($taskTester->afterBatchUpdateObject($taskIdList, $changeChildConsumed)) && p() && e('1'); // 检查修改子任务消耗工时的数据处理 diff --git a/module/task/test/model/assign.php b/module/task/test/model/assign.php index 7e65a4b4f0..486487dfa8 100755 --- a/module/task/test/model/assign.php +++ b/module/task/test/model/assign.php @@ -2,6 +2,7 @@ assignTest($taskIDlist[1], $doingTask)) && p('0:field,old,new') && r($task->assignTest($taskIDlist[1], $doingTaskLeft)) && p() && e('『预计剩余』不能为空。'); // doing状态任务指派,预计剩余为0 r($task->assignTest($taskIDlist[2], $doneTask)) && p('0:field,old,new') && e('assignedTo,old3,user14'); // done状态任务指派 r($task->assignTest($taskIDlist[3], $pauseTask)) && p('0:field,old,new') && e('assignedTo,old4,user15'); // pause状态任务指派 -r($task->assignTest($taskIDlist[4], $cancelTask)) && p('0:field,old,new') && e('assignedTo,old5,user16'); // cancel状态任务指派 \ No newline at end of file +r($task->assignTest($taskIDlist[4], $cancelTask)) && p('0:field,old,new') && e('assignedTo,old5,user16'); // cancel状态任务指派 diff --git a/module/task/test/model/batchupdate.php b/module/task/test/model/batchupdate.php index b38faee3b7..169d8db876 100755 --- a/module/task/test/model/batchupdate.php +++ b/module/task/test/model/batchupdate.php @@ -2,6 +2,12 @@ gen(5); +zdTable('project')->config('project')->gen(5); +zdTable('task')->config('task')->gen(20); +zdTable('taskspec')->config('taskspec')->gen(20); /** @@ -9,23 +15,54 @@ title=测试taskModel->batchUpdate(); timeout=0 cid=1 -- 测试批量修改任务 - - 第0条的field属性 @name - - 第0条的old属性 @开发任务11 - - 第0条的new属性 @任务改名1 - */ -$task = zdTable('task')->gen(10); -$taskSpec = zdTable('taskspec')->gen(10); +$taskIdList = range(1, 20); -$names = array(1 => '任务改名1', 2 => '任务改名2', 3 => '任务改名3'); -$type = array(1 => 'devel', 2 => 'design', 3 => 'test'); -$statuses = array(1 => 'doing', 2 => 'wait', 3 => 'done'); -$colors = array(1 => '#ff4e3e', 2 => '', 3 => ''); +$changeName = array('id' => 1, 'name' => '修改1'); +$changeModule = array('id' => 2, 'module' => 1); +$changeType = array('id' => 3, 'type' => 'devel'); +$changeAssignedTo = array('id' => 4, 'assignedTo' => 'admin'); +$changeStatus = array('id' => 5, 'status' => 'done'); +$changeEstStarted = array('id' => 6, 'estStarted' => '2023-05-25'); +$changeDeadline = array('id' => 7, 'estStarted' => '2023-01-02', 'deadline' => '2023-05-25'); +$changePri = array('id' => 8, 'pri' => 1); +$changeEstimate = array('id' => 9, 'estimate' => 1); +$changeConsumed = array('id' => 10, 'status' => 'doing', 'consumed' => 1); +$changeLeft = array('id' => 11, 'status' => 'doing', 'consumed' => 2, 'left' => 1); +$changeFinishedBy = array('id' => 12, 'status' => 'done', 'finishedBy' => 'admin'); +$changeClosedBy = array('id' => 13, 'status' => 'closed', 'closedBy' => 'admin', 'closedReason' => 'closed'); -$normal = array('taskIDList' => array(1 => 1, 2 => 2, 3 => 3), 'names' => $names, 'types' => $type); -$status = array('taskIDList' => array(1 => 1, 2 => 2, 3 => 3), 'names' => $names, 'types' => $type,'statuses'=> $statuses); +$emptyName = array('id' => 14, 'name' => ''); +$emptyType = array('id' => 15, 'type' => ''); +$emptyModule = array('id' => 16, 'module' => 0); +$emptyPri = array('id' => 17, 'pri' => 0); +$emptyEstimate = array('id' => 18, 'estimate' => 0); +$emptyEstStarted = array('id' => 19, 'estStarted' => ''); +$emptyDeadline = array('id' => 20, 'deadline' => ''); -$task = new taskTest(); -r($task->batchUpdateObject($normal)) && p('0:field,old,new') && e('name,开发任务11,任务改名1'); // 测试批量修改任务 +$taskTester = new taskTest(); + +/* Modify task field. */ +r($taskTester->batchUpdateObject($taskIdList, $changeName)) && p('1[0]:old,new') && e('开发任务11,修改1'); // 检查修改任务名称 +r($taskTester->batchUpdateObject($taskIdList, $changeModule)) && p('2[0]:old,new') && e('0,1'); // 检查修改任务模块 +r($taskTester->batchUpdateObject($taskIdList, $changeType)) && p('3[0]:old,new') && e('test,devel'); // 检查修改任务类型 +r($taskTester->batchUpdateObject($taskIdList, $changeAssignedTo)) && p('4[0]:old,new') && e('`^$`,admin'); // 检查修改任务指派人 +r($taskTester->batchUpdateObject($taskIdList, $changeStatus)) && p('5[0]:old,new') && e('wait,done'); // 检查修改任务状态 +r($taskTester->batchUpdateObject($taskIdList, $changeEstStarted)) && p('6[0]:old,new') && e('2023-01-02,2023-05-25'); // 检查修改任务开始日期 +r($taskTester->batchUpdateObject($taskIdList, $changeDeadline)) && p('7[0]:old,new') && e('2023-01-10,2023-05-25'); // 检查修改任务截止日期 +r($taskTester->batchUpdateObject($taskIdList, $changePri)) && p('8[0]:old,new') && e('3,1'); // 检查修改任务优先级 +r($taskTester->batchUpdateObject($taskIdList, $changeEstimate)) && p('9[0]:old,new') && e('3,1'); // 检查修改任务预计工时 +r($taskTester->batchUpdateObject($taskIdList, $changeConsumed)) && p('10[0]:old,new') && e('0,1'); // 检查修改任务消耗工时 +r($taskTester->batchUpdateObject($taskIdList, $changeLeft)) && p('11[1]:old,new') && e('3,1'); // 检查修改任务剩余工时 +r($taskTester->batchUpdateObject($taskIdList, $changeFinishedBy)) && p('12[1]:old,new') && e('`^$`,admin'); // 检查修改任务完成者 +r($taskTester->batchUpdateObject($taskIdList, $changeClosedBy)) && p('13[1]:old,new') && e('`^$`,admin'); // 检查修改任务关闭者 + +/* Check the required fields. */ +r($taskTester->batchUpdateObject($taskIdList, $emptyName, 'name')) && p('0') && e('『任务名称』不能为空。'); // 检查任务名称必填项 +r($taskTester->batchUpdateObject($taskIdList, $emptyType, 'type')) && p('0') && e('『任务类型』不能为空。'); // 检查任务类型必填项 +r($taskTester->batchUpdateObject($taskIdList, $emptyModule, 'module')) && p('0') && e('『所属模块』不能为空。'); // 检查任务所属模块必填项 +r($taskTester->batchUpdateObject($taskIdList, $emptyPri, 'pri')) && p('0') && e('『优先级』不能为空。'); // 检查任务优先级必填项 +r($taskTester->batchUpdateObject($taskIdList, $emptyEstimate, 'estimate')) && p('0') && e('『最初预计』不能为空。'); // 检查任务最初预计必填项 +r($taskTester->batchUpdateObject($taskIdList, $emptyEstStarted, 'estStarted')) && p('0') && e('『预计开始』不能为空。'); // 检查任务预计开始必填项 +r($taskTester->batchUpdateObject($taskIdList, $emptyDeadline, 'deadline')) && p('0') && e('『截止日期』不能为空。'); // 检查任务截止日期必填项 diff --git a/module/task/test/tao/getteammembersbyidlist.php b/module/task/test/model/getteammembersbyidlist.php similarity index 100% rename from module/task/test/tao/getteammembersbyidlist.php rename to module/task/test/model/getteammembersbyidlist.php diff --git a/module/task/test/model/yaml/afterbatchupdate/project.yaml b/module/task/test/model/yaml/afterbatchupdate/project.yaml new file mode 100755 index 0000000000..2a064b7281 --- /dev/null +++ b/module/task/test/model/yaml/afterbatchupdate/project.yaml @@ -0,0 +1,19 @@ +title: table zt_project +author: Hu Fangzhou +version: "1.0" +fields: + - field: id + range: 1-5 + - field: name + note: "名称" + fields: + - field: name1 + range: rnd + - field: name2 + range: 项目,迭代,阶段,看板{2} + - field: project + range: 1 + - field: type + range: project,sprint,stage,kanban{2} + - field: vision + range: rnd{4},lite diff --git a/module/task/test/model/yaml/afterbatchupdate/task.yaml b/module/task/test/model/yaml/afterbatchupdate/task.yaml new file mode 100755 index 0000000000..fbe773b3de --- /dev/null +++ b/module/task/test/model/yaml/afterbatchupdate/task.yaml @@ -0,0 +1,46 @@ +title: zt_task +author: Tian Shujie +version: "1.0" +fields: + - field: id + range: 1-100 + - field: module + range: 0 + - field: story + range: "1,0{14}" + - field: type + range: design,devel,test,study,discuss,ui,affair,misc + - field: status + range: wait + - field: pri + range: 3 + - field: parent + range: "0{13},1{2}" + - field: estimate + range: 3 + - field: consumed + range: 0 + - field: left + range: 3 + - field: project + range: 1 + - field: estStarted + range: "20230102 000000:0" + type: timestamp + format: "YY/MM/DD" + - field: deadline + range: "20230110 000000:0" + type: timestamp + format: "YY/MM/DD" + - field: execution + range: 2 + - field: assignedTo + range: '' + - field: deleted + range: 0 + - field: vision + range: rnd + - field: openedDate + range: "20220101 000000:1W" # 生成从2021年1月1日0时0分0秒到当前时间的数据,步长为1周 + type: timestamp + format: "YY/MM/DD hh:mm:ss" diff --git a/module/task/test/model/yaml/batchupdate/project.yaml b/module/task/test/model/yaml/batchupdate/project.yaml new file mode 100755 index 0000000000..2a064b7281 --- /dev/null +++ b/module/task/test/model/yaml/batchupdate/project.yaml @@ -0,0 +1,19 @@ +title: table zt_project +author: Hu Fangzhou +version: "1.0" +fields: + - field: id + range: 1-5 + - field: name + note: "名称" + fields: + - field: name1 + range: rnd + - field: name2 + range: 项目,迭代,阶段,看板{2} + - field: project + range: 1 + - field: type + range: project,sprint,stage,kanban{2} + - field: vision + range: rnd{4},lite diff --git a/module/task/test/model/yaml/batchupdate/task.yaml b/module/task/test/model/yaml/batchupdate/task.yaml new file mode 100755 index 0000000000..f25fb7c9d8 --- /dev/null +++ b/module/task/test/model/yaml/batchupdate/task.yaml @@ -0,0 +1,42 @@ +title: zt_task +author: Tian Shujie +version: "1.0" +fields: + - field: id + range: 1-100 + - field: module + range: 0 + - field: type + range: design,devel,test,study,discuss,ui,affair,misc + - field: status + range: wait + - field: pri + range: 3 + - field: estimate + range: 3 + - field: consumed + range: 0 + - field: left + range: 3 + - field: project + range: 1 + - field: estStarted + range: "20230102 000000:0" + type: timestamp + format: "YY/MM/DD" + - field: deadline + range: "20230110 000000:0" + type: timestamp + format: "YY/MM/DD" + - field: execution + range: 2 + - field: assignedTo + range: '' + - field: deleted + range: 0 + - field: vision + range: rnd + - field: openedDate + range: "20220101 000000:1W" # 生成从2021年1月1日0时0分0秒到当前时间的数据,步长为1周 + type: timestamp + format: "YY/MM/DD hh:mm:ss" diff --git a/module/task/test/model/yaml/batchupdate/taskspec.yaml b/module/task/test/model/yaml/batchupdate/taskspec.yaml new file mode 100644 index 0000000000..b6ba0ff391 --- /dev/null +++ b/module/task/test/model/yaml/batchupdate/taskspec.yaml @@ -0,0 +1,6 @@ +title: zt_taskspec +author: Tian Shujie +version: "1.0" +fields: + - field: task + range: 1-100 diff --git a/module/task/test/task.class.php b/module/task/test/task.class.php index 412c013a80..10c4f11b1b 100755 --- a/module/task/test/task.class.php +++ b/module/task/test/task.class.php @@ -113,51 +113,69 @@ class taskTest } /** - * Test batch update tasks. + * 批量更新任务。 + * Batch update tasks. * - * @param array $param + * @param array $taskIdList + * @param array $params + * @param string $requiredField * @access public * @return array */ - public function batchUpdateObject(array $param = array()) + public function batchUpdateObject(array $taskIdList, array $params = array(), $requiredField = ''): array { - $postData = new stdclass(); + $requiredFields = $this->objectModel->config->task->edit->requiredFields; + if($requiredField) $this->objectModel->config->task->edit->requiredFields = $this->objectModel->config->task->edit->requiredFields . ',' . $requiredField . ','; - foreach($param['taskIDList'] as $taskID) + $oldTasks = $this->objectModel->getByList($taskIdList); + $taskData = array(); + foreach($oldTasks as $task) { - $colors[$taskID] = ''; - $name[$taskID] = ''; - $modules[$taskID] = '0'; - $assignedTos[$taskID] = ''; - $types[$taskID] = ''; - $statuses[$taskID] = 'wait'; - $estStarteds[$taskID] = ''; - $deadlines[$taskID] = ''; - $pris[$taskID] = '3'; - $finishedBys[$taskID] = ''; - $canceledBys[$taskID] = ''; - $closedBys[$taskID] = ''; - $closedReasons[$taskID] = ''; - $consumeds[$taskID] = 0; - $lefts[$taskID] = 0; + if($params['id'] == $task->id) + { + foreach($params as $key => $value) + { + if($key == 'id') continue; + $task->$key = $value; + } + } + $taskData[$task->id] = $task; } - $createFields = array('modules' => $modules, 'names' => $name, 'types' => $types, 'assignedTos' => $assignedTos, - 'pris' => $pris, 'estStarteds' => $estStarteds, 'colors' => $colors, 'deadlines' => $deadlines, 'statuses' => $statuses, 'finishedBys'=>$finishedBys, - 'canceledBys' => $canceledBys, 'closedBys' => $closedBys, 'closedReasons' => $closedReasons, 'consumeds' => $consumeds, 'lefts'=> $lefts); - foreach($createFields as $field => $defaultValue) $postData->$field = $defaultValue; - foreach($param as $key => $value) $postData->$key = $value; + $allChanges = $this->objectModel->batchUpdate($taskData); + $this->objectModel->config->task->edit->requiredFields = $requiredFields; - $allChanges = $this->objectModel->batchUpdate($postData); + if(dao::isError()) return current(dao::getError()); + return $allChanges; + } - if(dao::isError()) + /** + * 批量编辑任务后的其他数据处理。 + * Other data process after task batch edit. + * + * @param array $taskIdList + * @param array $params + * @access public + * @return bool + */ + public function afterBatchUpdateObject(array $taskIdList, array $params): bool + { + $oldTasks = $this->objectModel->getByList($taskIdList); + $taskData = array(); + foreach($oldTasks as $task) { - return dao::getError(); - } - else - { - return array_shift($allChanges); + if($params['id'] == $task->id) + { + foreach($params as $key => $value) + { + if($key == 'id') continue; + $task->$key = $value; + } + } + $taskData[$task->id] = $task; } + + return $this->objectModel->afterBatchUpdate($taskData); } /** diff --git a/module/task/view/batchedit.html.php b/module/task/view/batchedit.html.php index d2f806f0f8..fe57209e56 100755 --- a/module/task/view/batchedit.html.php +++ b/module/task/view/batchedit.html.php @@ -78,22 +78,22 @@ js::set('requiredFields', $config->task->edit->requiredFields); - + $task):?> execution->getById($tasks[$taskID]->execution); - $modules = $this->tree->getTaskOptionMenu($tasks[$taskID]->execution, 0, 0, 'allModule'); + $prjInfo = $this->execution->getById($task->execution); + $modules = $this->tree->getTaskOptionMenu($task->execution, 0, 0, 'allModule'); foreach($modules as $moduleID => $moduleName) $modules[$moduleID] = $prjInfo->name. $moduleName; $modules = array('ditto' => $this->lang->task->ditto) + $modules; } ?> - assignedTo != $this->app->user->account and $tasks[$taskID]->mode == 'linear') or !isset($teams[$taskID][$app->user->account]))) ? "disabled='disabled'" : '';?> - parent < 0) ? "disabled='disabled'" : '';?> + assignedTo != $this->app->user->account and $task->mode == 'linear') or !isset($teams[$taskID][$app->user->account]))) ? "disabled='disabled'" : '';?> + parent < 0) ? "disabled='disabled'" : '';?> '', 'ditto' => $this->lang->task->ditto); - $teamAccounts = !empty($executionTeams[$tasks[$taskID]->execution]) ? array_keys($executionTeams[$tasks[$taskID]->execution]) : array(); + $teamAccounts = !empty($executionTeams[$task->execution]) ? array_column($executionTeams[$task->execution], 'account') : array(); foreach($teamAccounts as $teamAccount) $members[$teamAccount] = zget($users, $teamAccount); $taskMembers = array(); @@ -104,52 +104,52 @@ js::set('requiredFields', $config->task->edit->requiredFields); } else { - if($tasks[$taskID]->status == 'closed') $members['closed'] = 'Closed'; + if($task->status == 'closed') $members['closed'] = 'Closed'; $taskMembers = $members; } - if($tasks[$taskID]->assignedTo and !isset($taskMembers[$tasks[$taskID]->assignedTo])) + if($task->assignedTo and !isset($taskMembers[$task->assignedTo])) { - $taskMembers[$tasks[$taskID]->assignedTo] = $users[$tasks[$taskID]->assignedTo]; + $taskMembers[$task->assignedTo] = $users[$task->assignedTo]; } ?> - +
- name, "class='form-control'");?> + name, "class='form-control'");?>
- color, "data-provide='colorpicker' data-wrapper='input-control-icon-right' data-icon='color' data-btn-tip='{$lang->task->colorTag}' data-update-text='#names\\[{$taskID}\\]'");?> + color, "data-provide='colorpicker' data-wrapper='input-control-icon-right' data-icon='color' data-btn-tip='{$lang->task->colorTag}' data-update-text='#name\\[{$taskID}\\]'");?>
- ' style='overflow:visible'>module, "class='form-control picker-select' data-drop-width='auto'")?> - ' style='overflow:visible'>assignedTo, "class='form-control picker-select' data-drop-width='auto' {$disableAssignedTo}");?> - type, "class='form-control'");?> - >status, "class='form-control' {$disableHour}");?> - >estStarted) ? '' : $tasks[$taskID]->estStarted, "class='form-control text-center form-date'");?> - >deadline) ? '' : $tasks[$taskID]->deadline, "class='form-control text-center form-date'");?> - >pri, "class='form-control'");?> - >estimate, "class='form-control text-center' {$disableHour}");?> - > - >left, "class='form-control text-center' {$disableHour}");?> - ' style='overflow:visible'>finishedBy, "class='form-control picker-select' data-drop-width='auto'");?> - ' style='overflow:visible'>canceledBy, "class='form-control picker-select' data-drop-width='auto'");?> - ' style='overflow:visible'>closedBy, "class='form-control picker-select' data-drop-width='auto'");?> + ' style='overflow:visible'>module, "class='form-control picker-select' data-drop-width='auto'")?> + ' style='overflow:visible'>assignedTo, "class='form-control picker-select' data-drop-width='auto' {$disableAssignedTo}");?> + task->typeList, $task->type, "class='form-control'");?> + >task->statusList, $task->status, "class='form-control' {$disableHour}");?> + >estStarted) ? '' : $task->estStarted, "class='form-control text-center form-date'");?> + >deadline) ? '' : $task->deadline, "class='form-control text-center form-date'");?> + >task->priList, $task->pri, "class='form-control'");?> + >estimate, "class='form-control text-center' {$disableHour}");?> + > + >left, "class='form-control text-center' {$disableHour}");?> + ' style='overflow:visible'>finishedBy, "class='form-control picker-select' data-drop-width='auto'");?> + ' style='overflow:visible'>canceledBy, "class='form-control picker-select' data-drop-width='auto'");?> + ' style='overflow:visible'>closedBy, "class='form-control picker-select' data-drop-width='auto'");?> > closedReason; + $closedReason = $task->closedReason; if(!empty($closedReason)) { $reasonList[$closedReason] = $lang->task->reasonList[$closedReason]; } else { - $status = $tasks[$taskID]->status; + $status = $task->status; if($status == 'done' or $status == 'cancel') { $reasonList[$status] = $lang->task->reasonList[$status]; @@ -160,10 +160,10 @@ js::set('requiredFields', $config->task->edit->requiredFields); } } - echo html::select("closedReasons[$taskID]", $reasonList, $closedReason, 'class=form-control'); + echo html::select("closedReason[$taskID]", $reasonList, $closedReason, 'class=form-control'); ?> - control == 'select' or $extendField->control == 'multi-select') ? " style='overflow:visible'" : '') . ">" . $this->loadModel('flow')->getFieldControl($extendField, $tasks[$taskID], $extendField->field . "[{$taskID}]") . "";?> + control == 'select' or $extendField->control == 'multi-select') ? " style='overflow:visible'" : '') . ">" . $this->loadModel('flow')->getFieldControl($extendField, $task, $extendField->field . "[{$taskID}]") . "";?> diff --git a/module/task/zen.php b/module/task/zen.php index f351d90e62..4a6b81c54f 100644 --- a/module/task/zen.php +++ b/module/task/zen.php @@ -158,6 +158,63 @@ class taskZen extends task $this->view->lanePairs = $lanePairs; } + /** + * 展示批量编辑任务的相关变量。 + * Show the variables associated with the batch edit task. + * + * @param int $executionID + * @access protected + * @return void + */ + protected function assignBatchEditVars(int $executionID): void + { + /* Set menu and related variables. */ + if($executionID) + { + $this->execution->setMenu($executionID); + $execution = $this->execution->getById($executionID); + + $this->view->title = $execution->name . $this->lang->colon . $this->lang->task->batchEdit; + $this->view->execution = $execution; + $this->view->modules = $this->tree->getTaskOptionMenu($executionID, 0, 0, !empty($this->config->task->allModule) ? 'allModule' : ''); + } + else + { + $this->loadModel('my'); + $this->lang->my->menu->work['subModule'] = 'task'; + + $this->view->title = $this->lang->task->batchEdit; + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + } + + /* Check if the request data size exceeds the PHP limit. */ + $tasks = $this->task->getByList($this->post->taskIDList); + $countInputVars = count($tasks) * (count(explode(',', $this->config->task->custom->batchEditFields)) + 3); + $showSuhosinInfo = common::judgeSuhosinSetting($countInputVars); + if($showSuhosinInfo) $this->view->suhosinInfo = extension_loaded('suhosin') ? sprintf($this->lang->suhosinInfo, $countInputVars) : sprintf($this->lang->maxVarsInfo, $countInputVars); + + foreach(explode(',', $this->config->task->customBatchEditFields) as $field) + { + if(!empty($execution) && $execution->type == 'stage' && strpos('estStarted,deadline', $field) !== false) continue; + $customFields[$field] = $this->lang->task->$field; + } + $this->view->customFields = $customFields; + $this->view->showFields = $this->config->task->custom->batchEditFields; + + $executionIdList = array_unique(array_column($tasks, 'execution')); + + /* Assign. */ + $this->view->executionID = $executionID; + $this->view->tasks = $tasks; + $this->view->teams = $this->task->getTeamMembersByIdList($this->post->taskIDList); + $this->view->executionTeams = $this->execution->getMembersByIdList($executionIdList); + $this->view->executionName = zget($execution, 'name', ''); + $this->view->executionType = zget($execution, 'type', ''); + $this->view->users = $this->loadModel('user')->getPairs('nodeleted'); + + $this->display(); + } + /** * 构建任务编辑表单。 * Build task edit form. @@ -208,56 +265,6 @@ class taskZen extends task $this->display(); } - /** - * 创建批量编辑表单。 - * Build batch edit form. - * - * @param array $taskIdList - * @param int $executionID - * @access protected - * @return void - */ - protected function buildBatchEditForm(array $taskIdList, int $executionID): void - { - /* Get edited tasks. */ - $tasks = $this->dao->select('*')->from(TABLE_TASK)->where('id')->in($taskIdList)->fetchAll('id'); - $teams = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in($taskIdList)->fetchGroup('task', 'id'); - - /* Get execution teams. */ - $executionIdList = array(); - foreach($tasks as $task) if(!in_array($task->execution, $executionIdList)) $executionIdList[] = $task->execution; - $executionTeams = $this->dao->select('*')->from(TABLE_TEAM)->where('root')->in($executionIdList)->andWhere('type')->eq('execution')->fetchGroup('root', 'account'); - - /* Judge whether the editedTasks is too large and set session. */ - $countInputVars = count($tasks) * (count(explode(',', $this->config->task->custom->batchEditFields)) + 3); - $showSuhosinInfo = common::judgeSuhosinSetting($countInputVars); - if($showSuhosinInfo) $this->view->suhosinInfo = extension_loaded('suhosin') ? sprintf($this->lang->suhosinInfo, $countInputVars) : sprintf($this->lang->maxVarsInfo, $countInputVars); - - /* Set Custom. */ - foreach(explode(',', $this->config->task->customBatchEditFields) as $field) - { - if(!empty($this->view->executionn) && $this->view->execution->type == 'stage' && strpos('estStarted,deadline', $field) !== false) continue; - $customFields[$field] = $this->lang->task->$field; - } - $this->view->customFields = $customFields; - $this->view->showFields = $this->config->task->custom->batchEditFields; - - /* Assign. */ - $this->view->executionID = $executionID; - $this->view->priList = array('0' => '', 'ditto' => $this->lang->task->ditto) + $this->lang->task->priList; - $this->view->statusList = array('ditto' => $this->lang->task->ditto) + $this->lang->task->statusList; - $this->view->typeList = array('ditto' => $this->lang->task->ditto) + $this->lang->task->typeList; - $this->view->taskIDList = $taskIdList; - $this->view->tasks = $tasks; - $this->view->teams = $teams; - $this->view->executionTeams = $executionTeams; - $this->view->executionName = isset($execution) ? $execution->name : ''; - $this->view->executionType = isset($execution) ? $execution->type : ''; - $this->view->users = $this->loadModel('user')->getPairs('nodeleted'); - - $this->display(); - } - /** * 构建指派给表单。 * Build from for assignTo page. @@ -492,6 +499,49 @@ class taskZen extends task return $task; } + /** + * 构造批量编辑的任务数据。 + * Build the tasks data to batch edit. + * + * @access protected + * @return array + */ + protected function buildTasksForBatchEdit(): false|array + { + $taskData = form::batchData()->get(); + $oldTasks = $this->post->taskIDList ? $this->task->getByList($this->post->taskIDList) : array(); + $now = helper::now(); + foreach($taskData as $taskID => $task) + { + $oldTask = $oldTasks[$taskID]; + + $task->finishedDate = $oldTask->finishedBy == $task->finishedBy ? $oldTask->finishedDate : $now; + $task->canceledDate = $oldTask->canceledBy == $task->canceledBy ? $oldTask->canceledDate : $now; + $task->closedDate = $oldTask->closedBy == $task->closedBy ? $oldTask->closedDate : $now; + $task->parent = $oldTask->parent; + $task->assignedTo = $task->status == 'closed' ? 'closed' : $task->assignedTo; + $task->assignedDate = !empty($task->assignedTo) && $oldTask->assignedTo != $task->assignedTo ? $now : $oldTask->assignedDate; + $task->version = $oldTask->name != $task->name || $oldTask->estStarted != $task->estStarted || $oldTask->deadline != $task->deadline ? $oldTask->version + 1 : $oldTask->version; + $task->status = $task->status == 'done' && $task->closedReason ? 'closed' : $task->status; + $task->consumed = $task->consumed < 0 ? $task->consumed : $task->consumed + $oldTask->consumed; + + if(empty($task->closedReason) && $task->status == 'closed') + { + if($oldTask->status == 'done') $task->closedReason = 'done'; + if($oldTask->status == 'cancel') $task->closedReason = 'cancel'; + } + $task = $this->processTaskByStatus($task, $oldTask); + if($task->assignedTo) $task->assignedDate = $now; + + unset($task->taskIDList); + } + + $this->checkBatchEditTask($taskData, $oldTasks); + if(dao::isError()) return false; + + return $taskData; + } + /** * 构造激活的任务数据。 * Build the task data to activate. @@ -502,7 +552,8 @@ class taskZen extends task */ protected function buildTaskForActivate(int $taskID): object { - $task = form::data($this->config->task->form->activate)->add('id', $taskID)->get(); + $task = form::data()->add('id', $taskID)->get(); + unset($task->comment); return $this->loadModel('file')->processImgURL($task, $this->config->task->editor->activate['id'], $this->post->uid); } @@ -708,43 +759,6 @@ class taskZen extends task return $this->loadModel('file')->processImgURL($task, $this->config->task->editor->start['id'], $this->post->uid); } - /** - * 根据页面是执行还是我的地盘设置参数。 - * Set parameters based on whether the page is execution or my. - * - * @param int $executionID - * @access protected - * @return void - */ - protected function batchEdit4Pages(int $executionID): void - { - /* The tasks of execution. */ - if($executionID) - { - $execution = $this->execution->getById($executionID); - $this->execution->setMenu($execution->id); - - /* Set modules and members. */ - $showAllModule = isset($this->config->task->allModule) ? $this->config->task->allModule : ''; - $modules = $this->tree->getTaskOptionMenu($executionID, 0, 0, $showAllModule ? 'allModule' : ''); - $modules = array('ditto' => $this->lang->task->ditto) + $modules; - - $this->view->title = $execution->name . $this->lang->colon . $this->lang->task->batchEdit; - $this->view->execution = $execution; - $this->view->modules = $modules; - } - /* The tasks of my. */ - else - { - /* Set my menu. */ - $this->loadModel('my'); - $this->lang->my->menu->work['subModule'] = 'task'; - - $this->view->title = $this->lang->task->batchEdit; - $this->view->users = $this->loadModel('user')->getPairs('noletter'); - } - } - /** * 任务模块的一些常用操作。 * Common actions of task module. @@ -852,6 +866,39 @@ class taskZen extends task return true; } + /** + * 检查传入的批量编辑数据是否符合要求。 + * Check if the input post meets the requirements. + * + * @param array $tasks + * @param array $oldTasks + * @access protected + * @return bool + */ + protected function checkBatchEditTask(array $tasks, array $oldTasks): bool + { + foreach($tasks as $taskID => $task) + { + $oldTask = $oldTasks[$taskID]; + + /* Check work hours. */ + if(in_array($task->status, array('doing', 'pause')) && empty($oldTask->mode) && empty($task->left) && $task->parent >= 0) + { + dao::$errors["left[{$taskID}]"] = (array)sprintf($this->lang->task->error->leftEmptyAB, zget($this->lang->task->statusList, $task->status)); + } + if($task->estimate < 0) dao::$errors["estimate[$taskID]"] = (array)sprintf($this->lang->task->error->recordMinus, $this->lang->task->estimateAB); + if($task->consumed < 0 ) dao::$errors["consumed[{$taskID}]"] = (array)sprintf($this->lang->task->error->recordMinus, $this->lang->task->consumedThisTime); + if($task->left < 0) dao::$errors["left[$taskID]"] = (array)sprintf($this->lang->task->error->recordMinus, $this->lang->task->leftAB); + + if(!empty($this->config->limitTaskDate)) $this->task->checkEstStartedAndDeadline($oldTask->execution, $task->estStarted, $task->deadline, "task:{$taskID} "); + + if($task->status == 'cancel') continue; + if($task->status == 'done' && !$task->consumed) dao::$errors["consumed[{$taskID}]"] = (array)sprintf($this->lang->error->notempty, $this->lang->task->consumedThisTime); + if(!empty($task->deadline) && $task->estStarted > $task->deadline) dao::$errors["deadline[{$taskID}]"] = (array)$this->lang->task->error->deadlineSmall; + } + return !dao::isError(); + } + /** * 检查规定时间内是否创建了同名任务。 * Check whether a task with the same name is created within the specified time. @@ -1114,6 +1161,64 @@ class taskZen extends task ->get(); } + /** + * 通过任务状态处理任务的人员、日期字段。 + * Process the person and date fields of a task by status. + * + * @param object $task + * @param object $oldTask + * @access protected + * @return objecy + */ + protected function processTaskByStatus(object $task, object $oldTask): object + { + $now = helper::now(); + $currentAccount = $this->app->user->account; + switch($task->status) + { + case 'done': + $task->left = 0; + if(!$task->finishedBy) $task->finishedBy = $currentAccount; + if($task->closedReason) $task->closedDate = $now; + $task->finishedDate = $oldTask->status == 'done' ? $oldTask->finishedDate : $now; + $task->canceledBy = ''; + $task->canceledDate = null; + break; + case 'cancel': + $task->canceledBy = !$task->canceledBy ? $currentAccount : $task->canceledBy; + $task->canceledDate = !$task->canceledBy ? $now : $task->canceledDate; + $task->assignedTo = $oldTask->openedBy; + $task->assignedDate = $now; + $task->finishedBy = ''; + $task->finishedDate = null; + break; + case 'closed': + $task->closedBy = !$task->closedBy ? $currentAccount : $task->closedBy; + $task->closedDate = !$task->closedBy ? $now : $task->closedDate; + if($task->closedReason == 'cancel' and helper::isZeroDate($task->finishedDate)) $task->finishedDate = null; + break; + case 'wait': + if($task->consumed > 0 and $task->left > 0) $task->status = 'doing'; + if($task->left == $oldTask->left and $task->consumed == 0) $task->left = $task->estimate; + break; + case 'pause': + $task->finishedDate = null; + default: + break; + } + if(in_array($task->status, array('wait', 'doing'))) + { + $task->canceledBy = ''; + $task->finishedBy = ''; + $task->closedBy = ''; + $task->canceledDate = null; + $task->finishedDate = null; + $task->closedDate = null; + $task->closedReason = ''; + } + return $task; + } + /** * 编辑任务后返回响应. * Response after edit. @@ -1168,27 +1273,19 @@ class taskZen extends task { foreach($allChanges as $taskID => $changes) { - if(empty($changes)) continue; - - $actionID = $this->loadModel('action')->create('task', $taskID, 'Edited'); - $this->action->logHistory($actionID, $changes); - $task = $this->task->getById($taskID); - if($task->fromBug != 0) + if(!$task->fromBug) continue; + foreach($changes as $change) { - foreach($changes as $change) + if($change['field'] == 'status') { - if($change['field'] == 'status') - { - $response['callback'] = "parent.confirmBug('" . sprintf($this->lang->task->remindBug, $task->fromBug) . "', {$task->fromBug})"; - return $response; - } + $response['callback'] = "parent.confirmBug('" . sprintf($this->lang->task->remindBug, $task->fromBug) . "', {$task->fromBug})"; + return $response; } } } } - $this->loadModel('score')->create('ajax', 'batchOther'); $response['load'] = $this->session->taskList; return $response; }