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.
*