task.html.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. <?php
  2. /**
  3. * The task view file of execution module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
  6. * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Yanyi Cao<caoyanyi@easycorp.ltd>
  8. * @package execution
  9. * @link https://www.zentao.net
  10. */
  11. namespace zin;
  12. include($this->app->getModuleRoot() . 'ai/ui/promptmenu.html.php');
  13. /* zin: Define the set::module('task') feature bar on main menu. */
  14. if(empty($features['story'])) unset($lang->execution->featureBar['task']['needconfirm']);
  15. $queryMenuLink = createLink('execution', 'task', "executionID={$execution->id}&status=bySearch&param={queryID}");
  16. $isFromDoc = $from === 'doc';
  17. $isFromAI = $from === 'ai';
  18. jsVar('canAssignTo', common::canModify('execution', $execution) && hasPriv('task', 'assignTo'));
  19. if($isFromDoc || $isFromAI)
  20. {
  21. $this->app->loadLang('doc');
  22. $executions = $this->loadModel('execution')->getPairs();
  23. $executionChangeLink = createLink($app->rawModule, $app->rawMethod, "executionID={executionID}&status=$status&param=$param&orderBy=$orderBy&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}&from=$from&blockID=$blockID");
  24. $insertListLink = createLink($app->rawModule, $app->rawMethod, "executionID=$executionID&status=$status&param=$param&orderBy=$orderBy&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}&from=$from&blockID={blockID}");
  25. formPanel
  26. (
  27. setID('zentaolist'),
  28. setClass('mb-4-important'),
  29. set::title(sprintf($this->lang->doc->insertTitle, $this->lang->doc->zentaoList['task'])),
  30. set::actions(array()),
  31. set::showExtra(false),
  32. to::titleSuffix
  33. (
  34. span
  35. (
  36. setClass('text-muted text-sm text-gray-600 font-light'),
  37. span(setClass('text-warning mr-1'), icon('help')),
  38. $lang->doc->previewTip
  39. )
  40. ),
  41. formRow
  42. (
  43. formGroup
  44. (
  45. set::width('1/2'),
  46. set::name('execution'),
  47. set::label($lang->doc->execution),
  48. set::control(array('required' => false)),
  49. set::items($executions),
  50. set::value($executionID),
  51. set::required(),
  52. span
  53. (
  54. setClass('error-tip text-danger hidden'),
  55. $lang->doc->emptyError
  56. ),
  57. on::change('[name="execution"]')->do("loadModal('$executionChangeLink'.replace('{executionID}', $(this).val()))")
  58. )
  59. )
  60. );
  61. }
  62. featureBar
  63. (
  64. set::current($browseType),
  65. set::linkParams("executionID={$executionID}&status={key}&param={$param}&orderBy={$orderBy}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}&from=$from&blockID=$blockID"),
  66. set::isModal($isFromDoc || $isFromAI),
  67. set::queryMenuLinkCallback(array(function ($key) use ($queryMenuLink) {
  68. return str_replace('{queryID}', (string)$key, $queryMenuLink);
  69. })),
  70. li(searchToggle
  71. (
  72. set::simple($isFromDoc || $isFromAI),
  73. set::module('task'),
  74. set::open($browseType == 'bysearch'),
  75. ($isFromDoc || $isFromAI) ? set::target('#docSearchForm') : null,
  76. ($isFromDoc || $isFromAI) ? set::onSearch(jsRaw('function(){$(this.element).closest(".modal").find("#featureBar .nav-item>.active").removeClass("active").find(".label").hide()}')) : null
  77. ))
  78. );
  79. if($isFromDoc || $isFromAI)
  80. {
  81. div(setID('docSearchForm'));
  82. }
  83. /* zin: Define the toolbar on main menu. */
  84. $canCreate = common::canModify('execution', $execution) && hasPriv('task', 'create');
  85. $canBatchCreate = common::canModify('execution', $execution) && hasPriv('task', 'batchCreate');
  86. $canImportTask = common::canModify('execution', $execution) && hasPriv('execution', 'importTask');
  87. $canImportBug = common::canModify('execution', $execution) && hasPriv('execution', 'importBug');
  88. $this->loadModel('task');
  89. $importItems = array();
  90. if(common::canModify('execution', $execution))
  91. {
  92. $params = isset($moduleID) ? "&storyID=0&moduleID=$moduleID" : "";
  93. $batchCreateLink = $this->createLink('task', 'batchCreate', "executionID={$execution->id}{$params}") . ($app->tab == 'project' ? '#app=project' : '');
  94. $createLink = $this->createLink('task', 'create', "executionID={$execution->id}{$params}") . ($app->tab == 'project' ? '#app=project' : '');
  95. if(commonModel::isTutorialMode())
  96. {
  97. $wizardParams = helper::safe64Encode("executionID={$execution->id}{$params}");
  98. $taskCreateLink = $this->createLink('tutorial', 'wizard', "module=task&method=create&params=$wizardParams");
  99. }
  100. $createItem = array('text' => $lang->task->create, 'url' => $createLink);
  101. $batchCreateItem = array('text' => $lang->task->batchCreate, 'url' => $batchCreateLink);
  102. if($canImportTask && $execution->multiple) $importItems[] = array('text' => $lang->execution->importTask, 'url' => $this->createLink('execution', 'importTask', "execution={$execution->id}"));
  103. if($canImportBug && $execution->lifetime != 'ops' && !in_array($execution->attribute, array('request', 'review')))
  104. {
  105. $importItems[] = array('text' => $lang->execution->importBug, 'url' => $this->createLink('execution', 'importBug', "execution={$execution->id}"), 'className' => 'importBug', 'data-app' => $execution->multiple ? '' : 'project');
  106. }
  107. }
  108. $cols = $this->loadModel('datatable')->getSetting('execution');
  109. if($isFromDoc || $isFromAI)
  110. {
  111. if(isset($cols['actions'])) unset($cols['actions']);
  112. foreach($cols as $key => $col)
  113. {
  114. $cols[$key]['sortType'] = false;
  115. if(isset($col['link'])) unset($cols[$key]['link']);
  116. if($key == 'assignedTo') $cols[$key]['type'] = 'user';
  117. if($key == 'pri') $cols[$key]['priList'] = $lang->task->priList;
  118. if($key == 'name') $cols[$key]['link'] = array('url' => createLink('task', 'view', "taskID={id}"), 'data-toggle' => 'modal', 'data-size' => 'lg');
  119. }
  120. }
  121. if($execution->type != 'stage') unset($cols['design']);
  122. $canAssignTo = common::hasPriv('task', 'assignTo');
  123. $tableData = initTableData($tasks, $cols, $this->task);
  124. $lang->task->statusList['changed'] = $lang->task->storyChange;
  125. foreach($tableData as $task)
  126. {
  127. if(!isset($task->rawStatus)) $task->rawStatus = $task->status;
  128. $task->status = $this->processStatus('task', $task);
  129. $task->rawStory = $task->story;
  130. $task->story = $task->storyTitle;
  131. $task->canAssignTo = $canAssignTo ? common::hasDBPriv($task, 'task', 'assignTo') : false;
  132. if(helper::isZeroDate($task->deadline)) $task->deadline = '';
  133. if(helper::isZeroDate($task->estStarted)) $task->estStarted = '';
  134. $task = $this->task->processConfirmStoryChange($task);
  135. }
  136. if($config->edition == 'ipd')
  137. {
  138. $canStartExecution = $this->execution->checkStageStatus($execution->id, 'start');
  139. if(!empty($canStartExecution['disabled']))
  140. {
  141. foreach($tableData as $task)
  142. {
  143. foreach($task->actions as $key => $action)
  144. {
  145. if(in_array($action['name'], array('start', 'finish', 'recordWorkhour')))
  146. {
  147. $tip = $action['name'] . 'Tip';
  148. $task->actions[$key]['disabled'] = true;
  149. $task->actions[$key]['hint'] = $lang->task->disabledTip->$tip;
  150. }
  151. }
  152. }
  153. }
  154. }
  155. $viewType = $this->cookie->taskViewType ? $this->cookie->taskViewType : 'tree';
  156. toolbar
  157. (
  158. setClass(array('hidden' => $isFromDoc || $isFromAI)),
  159. item(set(array
  160. (
  161. 'type' => 'btnGroup',
  162. 'items' => array(array
  163. (
  164. 'icon' => 'list',
  165. 'class' => 'btn-icon switchButton' . ($viewType == 'tiled' ? ' text-primary' : ''),
  166. 'data-type' => 'tiled',
  167. 'hint' => $lang->task->viewTypeList['tiled']
  168. ), array
  169. (
  170. 'icon' => 'treeview',
  171. 'class' => 'switchButton btn-icon' . ($viewType == 'tree' ? ' text-primary' : ''),
  172. 'data-type' => 'tree',
  173. 'hint' => $lang->task->viewTypeList['tree']
  174. ))
  175. ))),
  176. hasPriv('task', 'report') && empty($execution->isTpl) ? item(set(array
  177. (
  178. 'icon' => 'bar-chart',
  179. 'class' => 'ghost',
  180. 'text' => $lang->task->report->common,
  181. 'data-app' => $app->tab,
  182. 'url' => createLink('task', 'report', "execution={$execution->id}&browseType={$browseType}")
  183. ))) : null,
  184. hasPriv('task', 'export') ? item(set(array
  185. (
  186. 'icon' => 'export',
  187. 'class' => 'ghost export',
  188. 'url' => createLink('task', 'export', "execution={$execution->id}&orderBy={$orderBy}&type={$browseType}"),
  189. 'data-toggle' => 'modal',
  190. 'text' => $lang->export
  191. ))) : null,
  192. $importItems ? dropdown(
  193. btn(
  194. setClass('ghost btn btn-default'),
  195. set::icon('import'),
  196. $lang->import
  197. ),
  198. set::items($importItems),
  199. set::placement('bottom-end')
  200. ) : null,
  201. $canCreate && $canBatchCreate ? btngroup
  202. (
  203. btn(setClass('btn primary createTask-btn'), set::icon('plus'), set::url($createLink), $lang->task->create),
  204. dropdown
  205. (
  206. btn(setClass('btn primary dropdown-toggle'),
  207. setStyle(array('padding' => '6px', 'border-radius' => '0 2px 2px 0'))),
  208. set::items(array_filter(array($createItem, $batchCreateItem))),
  209. set::placement('bottom-end')
  210. )
  211. ) : null,
  212. $canCreate && !$canBatchCreate ? item(set($createItem + array('class' => 'btn primary createTask-btn', 'icon' => 'plus'))) : null,
  213. $canBatchCreate && !$canCreate ? item(set($batchCreateItem + array('class' => 'btn primary', 'icon' => 'plus'))) : null
  214. );
  215. /* zin: Define the sidebar in main content. */
  216. $activeKey = $browseType == 'byproduct' ? $productID : $moduleID;
  217. if(!$isFromDoc && !$isFromAI)
  218. {
  219. sidebar
  220. (
  221. moduleMenu
  222. (
  223. set::modules($moduleTree),
  224. set::activeKey($status == 'byProduct' ? "product-{$activeKey}" : $activeKey),
  225. set::settingLink(createLink('tree', 'browsetask', "rootID=$execution->id&productID=0")),
  226. set::settingApp($execution->multiple ? 'execution' : 'project'),
  227. set::closeLink(createLink('execution', 'task', "executionID={$executionID}")),
  228. set::app($app->tab)
  229. )
  230. );
  231. }
  232. $firstTask = reset($tasks);
  233. $canBatchEdit = common::hasPriv('task', 'batchEdit', !empty($firstTask) ? $firstTask : null);
  234. $canBatchClose = common::hasPriv('task', 'batchClose', !empty($firstTask) ? $firstTask : null) && strtolower($browseType) != 'closed';
  235. $canBatchCancel = common::hasPriv('task', 'batchCancel', !empty($firstTask) ? $firstTask : null) && strtolower($browseType) != 'cancel';
  236. $canBatchAssignTo = common::hasPriv('task', 'batchAssignTo', !empty($firstTask) ? $firstTask : null);
  237. $canBatchChangeModule = common::hasPriv('task', 'batchChangeModule', !empty($firstTask) ? $firstTask : null);
  238. $canBatchAction = in_array(true, array($canBatchEdit, $canBatchClose, $canBatchCancel, $canBatchChangeModule, $canBatchAssignTo));
  239. $footToolbar = array();
  240. if($canBatchAction)
  241. {
  242. if($canBatchClose || $canBatchCancel)
  243. {
  244. $batchCancelClass = $config->edition == 'open' ? 'ajax-btn' : 'ajax-cancel-btn';
  245. $batchItems = array
  246. (
  247. array('text' => $lang->close, 'innerClass' => 'batch-btn ajax-btn not-open-url', 'disabled' => !$canBatchClose, 'data-url' => createLink('task', 'batchClose')),
  248. array('text' => $lang->task->cancel, 'innerClass' => "batch-btn $batchCancelClass not-open-url", 'disabled' => !$canBatchCancel, 'data-url' => createLink('task', 'batchCancel'))
  249. );
  250. }
  251. if($canBatchChangeModule)
  252. {
  253. $moduleItems = array();
  254. foreach($modules as $moduleID => $module)
  255. {
  256. $moduleItems[] = array('text' => $module, 'innerClass' => 'batch-btn ajax-btn', 'data-url' => createLink('task', 'batchChangeModule', "moduleID=$moduleID"));
  257. }
  258. }
  259. if($canBatchAssignTo)
  260. {
  261. $pinyinItems = common::convert2Pinyin($memberPairs);
  262. $assignedToItems = array();
  263. foreach($memberPairs as $account => $name)
  264. {
  265. $assignedToItems[] = array('text' => $name, 'keys' => zget($pinyinItems, $name, ''), 'innerClass' => 'batch-btn ajax-btn', 'data-url' => createLink('task', 'batchAssignTo', "executionID={$execution->id}&assignedTo={$account}"));
  266. }
  267. }
  268. if($canBatchClose || $canBatchCancel || $canBatchEdit)
  269. {
  270. $editClass = $canBatchEdit ? 'batch-btn' : '';
  271. $footToolbar['items'][] = array(
  272. 'type' => 'btn-group',
  273. 'items' => array(
  274. array('text' => $lang->edit, 'className' => "btn size-sm {$editClass}", 'disabled' => !$canBatchEdit , 'btnType' => 'secondary', 'data-url' => createLink('task', 'batchEdit', "executionID={$execution->id}")),
  275. array('caret' => 'up', 'className' => 'btn btn-caret size-sm not-open-url', 'btnType' => 'secondary', 'items' => $batchItems, 'data-placement' => 'top-start')
  276. )
  277. );
  278. }
  279. if($canBatchChangeModule) $footToolbar['items'][] = array('caret' => 'up', 'text' => $lang->task->moduleAB, 'className' => 'btn btn-caret size-sm', 'btnType' => 'secondary', 'items' => $moduleItems, 'type' => 'dropdown', 'data-placement' => 'top-start', 'data-menu' => array('searchBox' => true));
  280. if($canBatchAssignTo) $footToolbar['items'][] = array('caret' => 'up', 'text' => $lang->task->assignedTo, 'className' => 'btn btn-caret size-sm', 'btnType' => 'secondary', 'items' => $assignedToItems,'type' => 'dropdown');
  281. }
  282. if($isFromDoc) $footToolbar = array(array('text' => $lang->doc->insertText, 'data-on' => 'click', 'data-call' => "insertListToDoc('#tasks', 'task', $blockID, '$insertListLink')"));
  283. if($isFromAI) $footToolbar = array(array('text' => $lang->doc->insertText, 'data-on' => 'click', 'data-call' => "insertListToAI('#tasks', 'task')"));
  284. jsVar('+pageSummary', $lang->execution->pageSummary);
  285. jsVar('checkedSummary', $lang->execution->checkedSummary);
  286. jsVar('multipleAB', $lang->task->multipleAB);
  287. jsVar('childrenAB', $lang->task->childrenAB);
  288. jsVar('parentAB', $lang->task->parentAB);
  289. jsVar('todayLabel', $lang->today);
  290. jsVar('yesterdayLabel', $lang->yesterday);
  291. jsVar('teamLang', $lang->task->team);
  292. jsVar('delayWarning', $lang->task->delayWarning);
  293. if($viewType == 'tiled') $cols['name']['nestedToggle'] = false;
  294. $createTaskLink = $canCreate && common::canModify('execution', $execution) ? $createLink : '';
  295. dtable
  296. (
  297. set::id('tasks'),
  298. set::groupDivider(true),
  299. set::userMap($users),
  300. set::cols($cols),
  301. set::data($tableData),
  302. set::checkable($canBatchAction || $isFromDoc || $isFromAI),
  303. set::orderBy($orderBy),
  304. set::onRenderCell(jsRaw('window.renderCell')),
  305. set::modules($modulePairs),
  306. set::footToolbar($footToolbar),
  307. set::isFromDoc($isFromDoc || $isFromAI),
  308. set::footPager(usePager(array
  309. (
  310. 'recPerPage' => $pager->recPerPage,
  311. 'recTotal' => $pager->recTotal,
  312. 'linkCreator' => helper::createLink('execution', 'task', "executionID={$execution->id}&status={$status}&param={$param}&orderBy=$orderBy&recTotal={$pager->recTotal}&recPerPage={recPerPage}&page={page}&from={$from}&blockID={$blockID}") . "#app={$app->tab}"
  313. ))),
  314. !$isFromDoc ? null : set::afterRender(jsCallback()->call('toggleCheckRows', $idList)),
  315. (!$isFromDoc && !$isFromAI) ? null : set::onCheckChange(jsRaw('window.checkedChange')),
  316. (!$isFromDoc && !$isFromAI) ? null : set::noNestedCheck(true),
  317. (!$isFromDoc && !$isFromAI) ? null : set::height(400),
  318. ($isFromDoc || $isFromAI) ? null : set::customCols(true),
  319. ($isFromDoc || $isFromAI) ? null : set::sortLink(createLink('execution', 'task', "executionID={$execution->id}&status={$status}&param={$param}&orderBy={name}_{sortType}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}")),
  320. ($isFromDoc || $isFromAI) ? null : set::checkInfo(jsRaw('function(checkedIDList){return window.setStatistics(this, checkedIDList);}')),
  321. ($isFromDoc || $isFromAI) ? null : set::createTip($lang->task->create),
  322. ($isFromDoc || $isFromAI) ? null : set::createLink($createTaskLink),
  323. set::emptyTip($lang->task->noTask)
  324. );