diff --git a/module/task/config/form.php b/module/task/config/form.php index 1338a5fa42..28a69266a9 100644 --- a/module/task/config/form.php +++ b/module/task/config/form.php @@ -6,6 +6,42 @@ $config->task->form->assign = array(); $config->task->form->assign['assignedTo'] = array('type' => 'string', 'required' => false, 'default' => ''); $config->task->form->assign['left'] = array('type' => 'float', 'required' => true); $config->task->form->assign['lastEditedBy'] = array('type' => 'string', 'required' => false, 'default' => $app->user->account); -$config->task->form->assign['lastEditedDate'] = array('type' => 'date', 'required' => false, 'default' => helper::now()); -$config->task->form->assign['assignedDate'] = array('type' => 'date', 'required' => false, 'default' => helper::now()); +$config->task->form->assign['lastEditedDate'] = array('type' => 'string', 'required' => false, 'default' => helper::now()); +$config->task->form->assign['assignedDate'] = array('type' => 'string', 'required' => false, 'default' => helper::now()); $config->task->form->assign['comment'] = array('type' => 'text', 'required' => false, 'default' => ''); + +$config->task->form->edit = array(); +$config->task->form->edit['name'] = array('type' => 'string', 'required' => true); +$config->task->form->edit['color'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['desc'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['comment'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['lastEditedBy'] = array('type' => 'string', 'required' => false, 'default' => $app->user->account); +$config->task->form->edit['execution'] = array('type' => 'int', 'required' => true); +$config->task->form->edit['story'] = array('type' => 'int', 'required' => false, 'default' => 0); +$config->task->form->edit['module'] = array('type' => 'int', 'required' => false, 'default' => 0); +$config->task->form->edit['parent'] = array('type' => 'int', 'required' => false, 'default' => 0); +$config->task->form->edit['mailto'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['mode'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['assignedTo'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['type'] = array('type' => 'string', 'required' => true); +$config->task->form->edit['status'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['pri'] = array('type' => 'int', 'required' => false, 'default' => 0); +$config->task->form->edit['estStarted'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['deadline'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['estimate'] = array('type' => 'float', 'required' => false, 'default' => 0); +$config->task->form->edit['left'] = array('type' => 'float', 'required' => false, 'default' => 0); +$config->task->form->edit['consumed'] = array('type' => 'float', 'required' => false, 'default' => 0); +$config->task->form->edit['realStarted'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['finishedBy'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['finishedDate'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['canceledBy'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['canceledDate'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['closedBy'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['closedReason'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['closedDate'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->task->form->edit['team'] = array('type' => 'array', 'required' => false, 'default' => array()); +$config->task->form->edit['teamSource'] = array('type' => 'array', 'required' => false, 'default' => array()); +$config->task->form->edit['teamEstimate'] = array('type' => 'array', 'required' => false, 'default' => array()); +$config->task->form->edit['teamConsumed'] = array('type' => 'array', 'required' => false, 'default' => array()); +$config->task->form->edit['teamLeft'] = array('type' => 'array', 'required' => false, 'default' => array()); +$config->task->form->edit['deleteFiles'] = array('type' => 'array', 'required' => false, 'default' => array()); diff --git a/module/task/control.php b/module/task/control.php index 14bef201c0..f59e4a0ce8 100755 --- a/module/task/control.php +++ b/module/task/control.php @@ -554,97 +554,37 @@ class task extends control public function edit($taskID, $comment = 'false', $kanbanGroup = 'default', $from = '') { $this->commonAction($taskID); - $task = $this->task->getById($taskID); if(!empty($_POST)) { $this->loadModel('action'); $changes = array(); + + $postDataFixer = form::data($this->config->task->form->edit); + $postData = $postDataFixer->rawdata; + if(!$comment or $comment == 'false') { - $changes = $this->task->update($taskID); + $task = $this->taskZen->prepareCreateExtras($postDataFixer, $taskID); + $changes = $this->task->update($task, $postData); if(dao::isError()) return print(js::error(dao::getError())); } - if($this->post->comment != '' or !empty($changes)) + if($postData->comment != '' or !empty($changes)) { - $action = !empty($changes) ? 'Edited' : 'Commented'; - $actionID = $this->action->create('task', $taskID, $action, $this->post->comment); + $action = !empty($changes) ? 'Edited' : 'Commented'; + $actionID = $this->action->create('task', $taskID, $action, $postData->comment); if(!empty($changes)) $this->action->logHistory($actionID, $changes); } $this->executeHooks($taskID); - if($_POST['status'] == 'doing') $this->loadModel('common')->syncPPEStatus($taskID); + if($postData->status == 'doing') $this->loadModel('common')->syncPPEStatus($taskID); - if($task->fromBug != 0) - { - foreach($changes as $change) - { - if($change['field'] == 'status') - { - $confirmURL = $this->createLink('bug', 'view', "id=$task->fromBug"); - $cancelURL = $this->server->HTTP_REFERER; - return print(js::confirm(sprintf($this->lang->task->remindBug, $task->fromBug), $confirmURL, $cancelURL, 'parent', 'parent')); - } - } - } - - if(isonlybody()) - { - $execution = $this->execution->getByID($task->execution); - $execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all'; - $execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default'; - if(($this->app->tab == 'execution' or ($this->config->vision == 'lite' and $this->app->tab == 'project')) and $execution->type == 'kanban') - { - $rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : ''; - $kanbanData = $this->loadModel('kanban')->getRDKanban($task->execution, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue); - $kanbanData = json_encode($kanbanData); - - return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)")); - } - if($from == 'taskkanban') - { - $taskSearchValue = $this->session->taskSearchValue ? $this->session->taskSearchValue : ''; - $kanbanData = $this->loadModel('kanban')->getExecutionKanban($task->execution, $execLaneType, $execGroupBy, $taskSearchValue); - $kanbanType = $execLaneType == 'all' ? 'task' : key($kanbanData); - $kanbanData = $kanbanData[$kanbanType]; - $kanbanData = json_encode($kanbanData); - - return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban(\"task\", $kanbanData)")); - } - return print(js::reload('parent.parent')); - } - - if(defined('RUN_MODE') && RUN_MODE == 'api') - { - return $this->send(array('status' => 'success', 'data' => $taskID)); - } - else - { - return print(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent')); - } + return $this->taskZen->reponseAfterEdit($taskID, $from); } - $tasks = $this->task->getParentTaskPairs($this->view->execution->id, $this->view->task->parent); - if(isset($tasks[$taskID])) unset($tasks[$taskID]); - - if(!isset($this->view->members[$this->view->task->assignedTo])) $this->view->members[$this->view->task->assignedTo] = $this->view->task->assignedTo; - if(isset($this->view->members['closed']) or $this->view->task->status == 'closed') $this->view->members['closed'] = 'Closed'; - - $executions = array(); - if(!empty($task->project)) $executions = $this->execution->getByProject($task->project, 'all', 0, true); - - $this->view->title = $this->lang->task->edit . 'TASK' . $this->lang->colon . $this->view->task->name; - $this->view->position[] = $this->lang->task->common; - $this->view->position[] = $this->lang->task->edit; - $this->view->stories = $this->story->getExecutionStoryPairs($this->view->execution->id, 0, 'all', '', 'full', 'active'); - $this->view->tasks = $tasks; - $this->view->users = $this->loadModel('user')->getPairs('nodeleted|noclosed', "{$this->view->task->openedBy},{$this->view->task->canceledBy},{$this->view->task->closedBy}"); - $this->view->showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : ''; - $this->view->modules = $this->tree->getTaskOptionMenu($this->view->task->execution, 0, 0, $this->view->showAllModule ? 'allModule' : ''); - $this->view->executions = $executions; - $this->display(); + $this->taskZen->buildEditForm($taskID); } /** @@ -808,19 +748,19 @@ class task extends control if(!empty($_POST)) { - $data = form::data($this->config->task->form->assign)->get(); - $postComment = $data->comment; - unset($data->comment); + $postDataFixer = form::data($this->config->task->form->assign); + $postData = $postDataFixer->rawdata; - $changes = $this->task->assign($data, $taskID); + $task = $this->taskZen->prepareAssignToExtras($postDataFixer, $taskID); + $changes = $this->task->assign($task); if(dao::isError()) return $this->taskZen->errorAfterAssignTo(); - $actionID = $this->loadModel('action')->create('task', $taskID, 'Assigned', $postComment, $this->post->assignedTo); + $actionID = $this->loadModel('action')->create('task', $taskID, 'Assigned', $postData->comment, $task->assignedTo); $this->action->logHistory($actionID, $changes); $this->executeHooks($taskID); - return $this->taskZen->reponseAfterAssignTo($changes); + return $this->taskZen->reponseAfterAssignTo($taskID); } $this->taskZen->buildAssignToForm($executionID, $task); diff --git a/module/task/css/edit.ui.css b/module/task/css/edit.ui.css new file mode 100644 index 0000000000..5492e1306d --- /dev/null +++ b/module/task/css/edit.ui.css @@ -0,0 +1,9 @@ +.pl-10px {padding-left: 10px;} +#showAllModuleBox{width:60px;} +#showAllModuleBox .no-margin {padding-left:22px;} +#taskTeamEditor .sortable .input-group .input-group-addon.required:after {top: 10px; right: 2px; z-index: 3;} +#storyIdBox .chosen-container .chosen-drop.chosen-no-wrap>.chosen-results>li {text-overflow: unset;} + +#modalTeam .modal-content {padding: 20px 13px 0; height: 340px;max-height: 296px!important; overflow-y: scroll!important;} +#taskTeamEditor tr .input-control {display: inline-block; width: 130px;} +#taskTeamEditor h2 {margin-top:0;} diff --git a/module/task/js/edit.ui.js b/module/task/js/edit.ui.js new file mode 100644 index 0000000000..f869ce5228 --- /dev/null +++ b/module/task/js/edit.ui.js @@ -0,0 +1,233 @@ +$(function() +{ + $('.record-estimate-toggle').modalTrigger({width:900, type:'iframe', afterHide: function(){parent.location.href=parent.location.href;}}); + if(!newRowCount) $('#taskTeamEditor tr.member').last().addClass('member-last'); + + if($('#consumedSpan').parent().find('button').hasClass('disabled')) + { + $('#consumedSpan').parent().find('button').attr('disabled','disabled') + } + + $('#mode').change(function() + { + var mode = $(this).val(); + if(mode != 'single') + { + if(mode != 'multi') + { + $('#assignedTo').attr('disabled', 'disabled').trigger('chosen:updated') + } + else + { + $('#assignedTo').removeAttr('disabled').trigger('chosen:updated') + } + + $('.team-group').removeClass('hidden'); + $('#estimate').attr('readonly', 'readonly'); + $('#left').attr('readonly', 'readonly'); + } + else + { + $('#assignedTo').removeAttr('disabled').trigger('chosen:updated') + $('.team-group').addClass('hidden'); + $('#estimate').removeAttr('readonly'); + $('#left').removeAttr('readonly'); + } + }) +}) + +/** + * Load module, stories and members. + * + * @param int $executionID + * @access public + * @return void + */ +function loadAll(executionID) +{ + if(confirm(confirmChangeExecution)) + { + loadModuleMenu(executionID); + loadExecutionStories(executionID); + loadExecutionMembers(executionID); + } + else + { + $('#execution').val(oldExecutionID); + $("#execution").trigger("chosen:updated"); + } +} + +/** + * Load module of the execution. + * + * @param int $executionID + * @access public + * @return void + */ +function loadModuleMenu(executionID) +{ + var link = createLink('tree', 'ajaxGetOptionMenu', 'rootID=' + executionID + '&viewtype=task'); + $('#moduleIdBox').load(link, function(){$('#module').chosen();}); +} + +/** + * Load stories of the execution. + * + * @param int $executionID + * @access public + * @return void + */ +function loadExecutionStories(executionID, moduleID) +{ + if(typeof(moduleID) == 'undefined') moduleID = 0; + var link = createLink('story', 'ajaxGetExecutionStories', 'executionID=' + executionID + '&productID=0&branch=0&moduleID=' + moduleID + '&storyID=' + oldStoryID); + $('#storyIdBox').load(link, function(){$('#story').chosen();}); +} + +/** + * Load team members of the execution. + * + * @param int $executionID + * @access public + * @return void + */ +function loadExecutionMembers(executionID) +{ + var link = createLink('execution', 'ajaxGetMembers', 'executionID=' + executionID + '&assignedTo=' + oldAssignedTo); + $('#assignedToIdBox').load(link, function(){$('#assignedToIdBox').find('select').chosen()}); +} + +/* empty function. */ +function setPreview(){} + +$(document).ready(function() +{ + $('#showAllModule').change(function() + { + var moduleID = $('#moduleIdBox #module').val(); + var extra = $(this).prop('checked') ? 'allModule' : ''; + $('#moduleIdBox').load(createLink('tree', 'ajaxGetOptionMenu', "rootID=" + executionID + '&viewType=task&branch=0&rootModuleID=0&returnType=html&fieldID=&needManage=0&extra=' + extra), function() + { + $('#moduleIdBox #module').val(moduleID).chosen(); + }); + }); +}); + +$('#confirmButton').click(function() +{ + var memberCount = ''; + var totalEstimate = 0; + var totalConsumed = oldConsumed; + var totalLeft = 0; + var error = false; + $('select[name^=team]').each(function() + { + if($(this).val() == '') return; + + memberCount++; + + var $tr = $(this).closest('tr'); + var account = $(this).find('option:selected').text(); + + var estimate = parseFloat($tr.find('[name^=teamEstimate]').val()); + if(!isNaN(estimate)) totalEstimate += estimate; + if(isNaN(estimate) || estimate <= 0) + { + bootbox.alert(account + ' ' + estimateNotEmpty); + error = true; + return false; + } + + var consumed = parseFloat($tr.find('[name^=teamConsumed]').val()); + if(!isNaN(consumed)) totalConsumed += consumed; + + var $left = $tr.find('[name^=teamLeft]'); + var left = parseFloat($left.val()); + if(!isNaN(left)) totalLeft += left; + if(!$left.prop('readonly') && (isNaN(left) || left <= 0) && team.length > 0) + { + bootbox.alert(account + ' ' + leftNotEmpty); + error = true; + return false; + } + + if(estimate == 0 || isNaN(estimate)) + { + $(this).val('').trigger("chosen:updated"); + bootbox.alert(estimateNotEmpty); + error = true; + return false; + } + }) + + if(error) return false; + + if(memberCount < 2) + { + bootbox.alert(teamMemberError); + return false; + } + + if(totalLeft == 0 && (taskStatus == 'doing' || taskStatus == 'pause')) + { + bootbox.alert(totalLeftError); + return false; + } + + $('#estimate').val(totalEstimate); + $('#consumedSpan').html(totalConsumed); + $('#left').val(totalLeft); + updateAssignedTo(); + + $('.close').click(); +}); + +/** + * Update assignedTo. + * + * @access public + * @return void + */ +function updateAssignedTo() +{ + var html = ''; + var mode = $('#mode').val(); + var assignedTo = $('#assignedTo').val(); + if(mode != 'single') + { + var isTeamMember = false; + $('select[name^=team]').each(function() + { + if($(this).find('option:selected').text() == '') return; + if($(this).val() == currentUser) isTeamMember = true; + + var account = $(this).find('option:selected').val(); + var realName = $(this).find('option:selected').text(); + var selected = account == assignedTo ? 'selected' : ''; + + html += ""; + }); + + if(mode == 'multi' && isTeamMember && mode != 'linear') + { + $('[name=assignedTo]').removeAttr('disabled').trigger('chosen:updated'); + } + else + { + $('[name=assignedTo]').attr('disabled', 'disabled').trigger('chosen:updated'); + } + } + else + { + for(key in members) + { + var selected = key == assignedTo ? 'selected' : ''; + html += ""; + } + } + + $('#assignedTo').html(html); + if(mode != 'single' && mode == 'linear' && $('#modalTeam tr.member-doing').length == 0 && $('#modalTeam tr.member-wait').length >= 1) $('[name=assignedTo]').val($('#modalTeam tr.member-wait:first').find('select[name^=team]:first').val()); + $('#assignedTo').trigger('chosen:updated'); +} diff --git a/module/task/model.php b/module/task/model.php index d62f594b35..7e9b353905 100755 --- a/module/task/model.php +++ b/module/task/model.php @@ -1600,10 +1600,9 @@ class taskModel extends model * @access public * @return array */ - public function assign($task, $taskID): array|false + public function assign(object $task): array|false { - $task->id = $taskID; - $oldTask = $this->getById($taskID); + $oldTask = $this->getById($task->id); if($oldTask->status != 'done' and $oldTask->status != 'closed' and isset($task->left) and $task->left == 0) { @@ -1611,7 +1610,7 @@ class taskModel extends model return false; } - if($oldTask->parent > 0) $this->updateParentStatus($taskID); + if($oldTask->parent > 0) $this->updateParentStatus($task->id); $task = $this->loadModel('file')->processImgURL($task, $this->config->task->editor->assignto['id'], $this->post->uid); $this->dao->update(TABLE_TASK) @@ -1619,7 +1618,7 @@ class taskModel extends model ->autoCheck() ->check('left', 'float') ->checkFlow() - ->where('id')->eq($taskID) + ->where('id')->eq($task->id) ->exec(); if(!dao::isError()) return common::createChanges($oldTask, $task); diff --git a/module/task/ui/edit.html.php b/module/task/ui/edit.html.php new file mode 100644 index 0000000000..4140664979 --- /dev/null +++ b/module/task/ui/edit.html.php @@ -0,0 +1,427 @@ +story); +jsVar('oldAssignedTo', $task->assignedTo); +jsVar('oldExecutionID', $task->execution); +jsVar('oldConsumed', $task->consumed); +jsVar('taskStatus', $task->status); +jsVar('currentUser', $app->user->account); +jsVar('team', $task->members); +jsVar('members', $members); +jsVar('page', 'edit'); +jsVar('confirmChangeExecution', $lang->task->confirmChangeExecution); +jsVar('teamMemberError', $lang->task->error->teamMember); +jsVar('totalLeftError', sprintf($this->lang->task->error->leftEmptyAB, $this->lang->task->statusList[$task->status])); +jsVar('estimateNotEmpty', sprintf($lang->error->gt, $lang->task->estimate, '0')); +jsVar('leftNotEmpty', sprintf($lang->error->gt, $lang->task->left, '0')); +jsVar('requiredFields', $config->task->edit->requiredFields); + +/* zin: Set variables to define picker options for form */ +$formTitle = $task->name; +$executionOptions = $executions; +$moduleOptions = $modules; +$storyOptions = $stories; +if($task->status == 'wait' and $task->parent == 0) +{ + $modeOptions = $lang->task->editModeList; +} +else +{ + if($task->mode == '') + { + $modeOptions = $lang->task->editModeList['single']; + } + else + { + $modeOptions = zget($lang->task->editModeList, $task->mode); + } +} +$assignedToOptions = $taskMembers; +$typeOptions = $lang->task->typeList; +$statusOptions = (array)$lang->task->statusList; +$priOptions = $lang->task->priList; +$mailtoOptions = $task->mailto; +$contactListMenuOptions = $contactLists; +$finishedByOptions = $members; +$canceledByOptions = $users; +$closedByOptions = $users; +$closedReasonOptions = $lang->task->reasonList; +$teamOptions = array(array('value' => "", 'text' => ""), array('value' => "zenggang", 'text' => "Z:曾刚")); +$teamOptions = array(array('value' => "", 'text' => ""), array('value' => "zenggang", 'text' => "Z:曾刚")); +$teamOptions = array(array('value' => "", 'text' => ""), array('value' => "zenggang", 'text' => "Z:曾刚")); +$teamOptions = array(array('value' => "", 'text' => ""), array('value' => "zenggang", 'text' => "Z:曾刚")); +$teamOptions = array(array('value' => "", 'text' => ""), array('value' => "zenggang", 'text' => "Z:曾刚")); +$teamOptions = array(array('value' => "", 'text' => ""), array('value' => "zenggang", 'text' => "Z:曾刚")); + + +/* ====== Define the page structure with zin widgets ====== */ + +/* zin: Define the form in main content */ +formPanel +( + set::title($formTitle), // The form title is diffrent from the page title, + formGroup + ( + set::width("1/4"), + set::name("execution"), + set::label("所属执行"), + set::required(true), + set::value("50"), + set::control("picker"), + set::items($executionOptions) + ), + formGroup + ( + set::width("1/4"), + set::name("module"), + set::label("所属模块"), + set::value("0"), + set::control("picker"), + set::items($moduleOptions) + ), + formGroup + ( + set::width("1/4"), + set::name("story"), + set::label("相关软件需求"), + set::control("picker"), + set::items($storyOptions) + ), + formGroup + ( + set::width("1/4"), + set::name("mode"), + set::label("任务模式"), + set::value("linear"), + set::control("picker"), + set::items($modeOptions) + ), + formGroup + ( + set::width("1/4"), + set::label("指派给"), + inputGroup + ( + control(set(array + ( + 'name' => "assignedTo", + 'id' => "assignedTo", + 'value' => "zenggang", + 'disabled' => true, + 'type' => "picker", + 'items' => $assignedToOptions + ))), + btn(set(array + ( + 'type' => "btn", + 'text' => "团队", + 'class' => "input-group-btn team-group" + ))) + ) + ), + formGroup + ( + set::width("1/4"), + set::name("type"), + set::label("任务类型"), + set::required(true), + set::value("study"), + set::control("picker"), + set::items($typeOptions) + ), + formGroup + ( + set::width("1/4"), + set::name("status"), + set::label("任务状态"), + set::value("wait"), + set::control("picker"), + set::items($statusOptions) + ), + formGroup + ( + set::width("1/4"), + set::name("pri"), + set::label("优先级"), + set::value("3"), + set::control("picker"), + set::items($priOptions) + ), + formGroup + ( + set::width("1/4"), + set::label("抄送给"), + inputGroup + ( + control(set(array + ( + 'name' => "mailto[]", + 'id' => "mailto", + 'value' => NULL, + 'disabled' => false, + 'type' => "picker", + 'items' => $mailtoOptions, + 'multiple' => true + ))), + control(set(array + ( + 'name' => "contactListMenu", + 'id' => "contactListMenu", + 'value' => "", + 'disabled' => false, + 'type' => "picker", + 'onchange' => "setMailto", + 'items' => $contactListMenuOptions + ))) + ) + ), + formGroup + ( + set::width("1/4"), + set::name("estStarted"), + set::label("预计开始"), + set::value("2023-04-25"), + set::control("date") + ), + formGroup + ( + set::width("1/4"), + set::name("deadline"), + set::label("截止日期"), + set::value("2023-04-28"), + set::control("date") + ), + formGroup + ( + set::width("1/4"), + set::name("estimate"), + set::label("最初预计"), + set::value("7"), + set::control("text") + ), + formGroup + ( + set::width("1/4"), + set::name("left"), + set::label("预计剩余"), + set::value("7"), + set::control("text") + ), + formGroup + ( + set::width("1/4"), + set::name("realStarted"), + set::label("实际开始"), + set::value("2023-04-24 17:25:00"), + set::control("datetime") + ), + formGroup + ( + set::width("1/4"), + set::name("finishedBy"), + set::label("由谁完成"), + set::control("picker"), + set::items($finishedByOptions) + ), + formGroup + ( + set::width("1/4"), + set::name("finishedDate"), + set::label("实际完成"), + set::value("0000-00-00 00:00:00"), + set::control("datetime") + ), + formGroup + ( + set::width("1/4"), + set::name("canceledBy"), + set::label("由谁取消"), + set::control("picker"), + set::items($canceledByOptions) + ), + formGroup + ( + set::width("1/4"), + set::name("canceledDate"), + set::label("取消时间"), + set::value("0000-00-00 00:00:00"), + set::control("datetime") + ), + formGroup + ( + set::width("1/4"), + set::name("closedBy"), + set::label("由谁关闭"), + set::control("picker"), + set::items($closedByOptions) + ), + formGroup + ( + set::width("1/4"), + set::name("closedReason"), + set::label("关闭原因"), + set::control("picker"), + set::items($closedReasonOptions) + ), + formGroup + ( + set::width("1/4"), + set::name("closedDate"), + set::label("关闭时间"), + set::value("0000-00-00 00:00:00"), + set::control("datetime") + ), + formRow + ( + set::hidden(true), + formGroup + ( + set::width("1/4"), + set::name("team[]"), + set::value("zenggang"), + set::control("picker"), + set::id("team"), + set::items($teamOptions) + ), + formGroup + ( + set::width("1/4"), + inputGroup + ( + + ) + ) + ), + formRow + ( + set::hidden(true), + formGroup + ( + set::width("1/4"), + set::name("team[]"), + set::value("zenggang"), + set::control("picker"), + set::id("team"), + set::items($teamOptions) + ), + formGroup + ( + set::width("1/4"), + inputGroup + ( + + ) + ) + ), + formRow + ( + set::hidden(true), + formGroup + ( + set::width("1/4"), + set::name("team[]"), + set::control("picker"), + set::id("team"), + set::items($teamOptions) + ), + formGroup + ( + set::width("1/4"), + inputGroup + ( + + ) + ) + ), + formRow + ( + set::hidden(true), + formGroup + ( + set::width("1/4"), + set::name("team[]"), + set::control("picker"), + set::id("team"), + set::items($teamOptions) + ), + formGroup + ( + set::width("1/4"), + inputGroup + ( + + ) + ) + ), + formRow + ( + set::hidden(true), + formGroup + ( + set::width("1/4"), + set::name("team[]"), + set::control("picker"), + set::id("team"), + set::items($teamOptions) + ), + formGroup + ( + set::width("1/4"), + inputGroup + ( + + ) + ) + ), + formRow + ( + set::hidden(true), + formGroup + ( + set::width("1/4"), + set::name("team[]"), + set::control("picker"), + set::id("team"), + set::items($teamOptions) + ), + formGroup + ( + set::width("1/4"), + inputGroup + ( + + ) + ) + ), + history + ( + set::actions($actions), + set::users($users), + set::methodName($methodName), + ) +); + + +/* ====== Render page ====== */ + +render(); diff --git a/module/task/zen.php b/module/task/zen.php index e0f5ad84c5..30a9070844 100644 --- a/module/task/zen.php +++ b/module/task/zen.php @@ -11,6 +11,187 @@ declare(strict_types=1); */ class taskZen extends task { + /** + * prepareCreateExtras + * + * @param object $postData + * @param int $taskID + * @access protected + * @return void + */ + protected function prepareCreateExtras(object $postDataFixer, int $taskID) + { + $oldTask = $this->task->getByID($taskID); + $now = helper::now(); + $postData = $postDataFixer->get(); + $task = $postDataFixer->add('id', $taskID) + ->setIF(!$postData->assignedTo and !empty($oldTask->team) and !empty($postData->team), 'assignedTo', $this->task->getAssignedTo4Multi($postData->team, $oldTask)) + ->setIF(!$oldTask->mode and !$postData->assignedTo and !empty($postData->team), 'assignedTo', $postData->team[0]) + ->setIF(is_numeric($postData->estimate), 'estimate', (float)$postData->estimate) + ->setIF(is_numeric($postData->consumed), 'consumed', (float)$postData->consumed) + ->setIF(is_numeric($postData->left), 'left', (float)$postData->left) + ->setIF($oldTask->parent == 0 && $postData->parent == '', 'parent', 0) + ->setIF(strpos($this->config->task->edit->requiredFields, 'estStarted') !== false, 'estStarted', $postData->estStarted) + ->setIF(strpos($this->config->task->edit->requiredFields, 'deadline') !== false, 'deadline', $postData->deadline) + ->setIF(strpos($this->config->task->edit->requiredFields, 'estimate') !== false, 'estimate', $postData->estimate) + ->setIF(strpos($this->config->task->edit->requiredFields, 'left') !== false, 'left', $postData->left) + ->setIF(strpos($this->config->task->edit->requiredFields, 'consumed') !== false, 'consumed', $postData->consumed) + ->setIF(strpos($this->config->task->edit->requiredFields, 'story') !== false, 'story', $postData->story) + ->setIF($postData->story != false and $postData->story != $oldTask->story, 'storyVersion', $this->loadModel('story')->getVersion($postData->story)) + + ->setIF($postData->mode == 'single', 'mode', '') + ->setIF($postData->status == 'done', 'left', 0) + ->setIF($postData->status == 'done' and !$postData->finishedBy, 'finishedBy', $this->app->user->account) + ->setIF($postData->status == 'done' and !$postData->finishedDate, 'finishedDate', $now) + + ->setIF($postData->status == 'cancel' and !$postData->canceledBy, 'canceledBy', $this->app->user->account) + ->setIF($postData->status == 'cancel' and !$postData->canceledDate, 'canceledDate', $now) + ->setIF($postData->status == 'cancel', 'assignedTo', $oldTask->openedBy) + ->setIF($postData->status == 'cancel', 'assignedDate', $now) + + ->setIF($postData->status == 'closed' and !$postData->closedBy, 'closedBy', $this->app->user->account) + ->setIF($postData->status == 'closed' and !$postData->closedDate, 'closedDate', $now) + ->setIF($postData->consumed > 0 and $postData->left > 0 and $postData->status == 'wait', 'status', 'doing') + + ->setIF($postData->assignedTo != $oldTask->assignedTo, 'assignedDate', $now) + + ->setIF($postData->status == 'wait' and $postData->left == $oldTask->left and $postData->consumed == 0 and $postData->estimate, 'left', $postData->estimate) + ->setIF($oldTask->parent > 0 and !$postData->parent, 'parent', 0) + ->setIF($oldTask->parent < 0, 'estimate', $oldTask->estimate) + ->setIF($oldTask->parent < 0, 'left', $oldTask->left) + + ->setIF($oldTask->name != $postData->name || $oldTask->estStarted != $postData->estStarted || $oldTask->deadline != $postData->deadline, 'version', $oldTask->version + 1) + + ->add('lastEditedDate', $now) + ->stripTags($this->config->task->editor->edit['id'], $this->config->allowedTags) + ->join('mailto', ',') + ->remove('comment,files,labels,uid,multiple,team,teamEstimate,teamConsumed,teamLeft,teamSource,contactListMenu') + ->get(); + + return $task; + } + + /** + * 编辑任务后返回响应. + * Reponse after edit. + * + * @param object $task + * @access protected + * @return int + */ + protected function reponseAfterEdit(int $taskID, string $from): int + { + $task = $this->task->getById($taskID); + if($task->fromBug != 0) + { + foreach($changes as $change) + { + if($change['field'] == 'status') + { + $confirmURL = $this->createLink('bug', 'view', "id=$task->fromBug"); + $cancelURL = $this->server->HTTP_REFERER; + return print(js::confirm(sprintf($this->lang->task->remindBug, $task->fromBug), $confirmURL, $cancelURL, 'parent', 'parent')); + } + } + } + + if(isonlybody()) + { + $execution = $this->execution->getByID($task->execution); + $execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all'; + $execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default'; + if(($this->app->tab == 'execution' or ($this->config->vision == 'lite' and $this->app->tab == 'project')) and $execution->type == 'kanban') + { + $rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : ''; + $kanbanData = $this->loadModel('kanban')->getRDKanban($task->execution, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue); + $kanbanData = json_encode($kanbanData); + + return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)")); + } + if($from == 'taskkanban') + { + $taskSearchValue = $this->session->taskSearchValue ? $this->session->taskSearchValue : ''; + $kanbanData = $this->loadModel('kanban')->getExecutionKanban($task->execution, $execLaneType, $execGroupBy, $taskSearchValue); + $kanbanType = $execLaneType == 'all' ? 'task' : key($kanbanData); + $kanbanData = $kanbanData[$kanbanType]; + $kanbanData = json_encode($kanbanData); + + return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban(\"task\", $kanbanData)")); + } + return print(js::reload('parent.parent')); + } + + if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success', 'data' => $taskID)); + return print(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent')); + } + + /** + * 构建任务编辑表格 + * Build task edit form. + * + * @param int $taskID + * @access protected + * @return void + */ + protected function buildEditForm(int $taskID): void + { + $task = $this->task->getById($taskID); + + $tasks = $this->task->getParentTaskPairs($this->view->execution->id, $this->view->task->parent); + if(isset($tasks[$taskID])) unset($tasks[$taskID]); + + if(!isset($this->view->members[$this->view->task->assignedTo])) $this->view->members[$this->view->task->assignedTo] = $this->view->task->assignedTo; + if(isset($this->view->members['closed']) or $this->view->task->status == 'closed') $this->view->members['closed'] = 'Closed'; + + $executions = array(); + if(!empty($task->project)) $executions = $this->execution->getByProject($task->project, 'all', 0, true); + + $taskMembers = array(); + if(!empty($task->team)) + { + $teamAccounts = $task->members; + foreach($teamAccounts as $teamAccount) + { + if(!isset($members[$teamAccount])) continue; + $taskMembers[$teamAccount] = $this->view->members[$teamAccount]; + } + } + else + { + $taskMembers = $this->view->members; + } + + $this->view->title = $this->lang->task->edit . 'TASK' . $this->lang->colon . $this->view->task->name; + $this->view->position[] = $this->lang->task->common; + $this->view->position[] = $this->lang->task->edit; + $this->view->stories = $this->story->getExecutionStoryPairs($this->view->execution->id, 0, 'all', '', 'full', 'active'); + $this->view->tasks = $tasks; + $this->view->taskMembers = $taskMembers; + $this->view->users = $this->loadModel('user')->getPairs('nodeleted|noclosed', "{$this->view->task->openedBy},{$this->view->task->canceledBy},{$this->view->task->closedBy}"); + $this->view->showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : ''; + $this->view->modules = $this->tree->getTaskOptionMenu($this->view->task->execution, 0, 0, $this->view->showAllModule ? 'allModule' : ''); + $this->view->executions = $executions; + $this->view->contactLists = $this->loadModel('user')->getContactLists($this->app->user->account, 'withnote'); + $this->display(); + } + + /** + * prepareAssignToExtras + * + * @param object $postDataFixer + * @param int $taskID + * @access protected + * @return void + */ + protected function prepareAssignToExtras(object $postDataFixer, int $taskID) + { + $task = $postDataFixer->add('id', $taskID) + ->stripTags($this->config->task->editor->assignto['id'], $this->config->allowedTags) + ->remove('comment,showModule') + ->get(); + return $task; + } + /** * Reponse after assignto. *