view.html.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. <?php
  2. /**
  3. * The view view file of productplan module of ZenTaoPMS.
  4. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
  5. * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  6. * @author Wang Yidong <yidong@easycorp.ltd>
  7. * @package productplan
  8. * @link https://www.zentao.net
  9. */
  10. namespace zin;
  11. include($this->app->getModuleRoot() . 'ai/ui/promptmenu.html.php');
  12. $unlinkURL['story'] = helper::createLink('productplan', 'unlinkStory', "storyID=%s&planID={$plan->id}");
  13. $unlinkURL['bug'] = helper::createLink('productplan', 'unlinkBug', "bugID=%s&planID={$plan->id}");
  14. $confirmLang['story'] = $lang->productplan->confirmUnlinkStory;
  15. $confirmLang['bug'] = $lang->productplan->confirmUnlinkBug;
  16. $confirmLang['start'] = $lang->productplan->confirmStart;
  17. $confirmLang['finish'] = $lang->productplan->confirmFinish;
  18. $confirmLang['activate'] = $lang->productplan->confirmActivate;
  19. $confirmLang['delete'] = $lang->productplan->confirmDelete;
  20. $decodeParam = helper::safe64Decode($param);
  21. $isInModal = isInModal();
  22. jsVar('initLink', $link);
  23. jsVar('type', $type);
  24. jsVar('linkParams', $decodeParam);
  25. jsVar('orderBy', $orderBy);
  26. jsVar('planID', $plan->id);
  27. jsVar('confirmLang', $confirmLang);
  28. jsVar('unlinkURL', $unlinkURL);
  29. jsVar('childrenAB', $lang->story->childrenAB);
  30. jsVar('cases', $storyCases);
  31. jsVar('summary', $summary);
  32. jsVar('checkedSummary', $lang->product->checkedSRSummary);
  33. jsVar('storyPageID', $storyPager->pageID);
  34. jsVar('storyRecPerPage', $storyPager->recPerPage);
  35. jsVar('gradeGroup', $gradeGroup);
  36. $bugCols = array();
  37. $storyCols = array();
  38. foreach($config->productplan->defaultFields['story'] as $field)
  39. {
  40. if($field == 'branch' && $product->type == 'normal') continue;
  41. $storyCols[$field] = zget($config->story->dtable->fieldList, $field, array());
  42. if($field == 'id' && common::hasPriv('execution', 'storySort'))
  43. {
  44. $storyCols['sort']['title'] = $lang->productplan->updateOrder;
  45. $storyCols['sort']['fixed'] = 'left';
  46. $storyCols['sort']['align'] = 'center';
  47. $storyCols['sort']['group'] = 1;
  48. $storyCols['sort']['width'] = 60;
  49. }
  50. }
  51. if(isset($storyCols['branch'])) $storyCols['branch']['map'] = $branchOption;
  52. foreach($config->productplan->defaultFields['bug'] as $field) $bugCols[$field] = zget($config->bug->dtable->fieldList, $field, array());
  53. $storyCols['title']['link'] = $this->createLink('story', 'storyView', "storyID={id}");
  54. $storyCols['title']['title'] = $lang->productplan->storyTitle;
  55. $storyCols['assignedTo']['type'] = 'user';
  56. $storyCols['module']['type'] = 'text';
  57. $storyCols['module']['map'] = $modulePairs;
  58. $storyCols['module']['sortType'] = true;
  59. $storyCols['actions']['list'] = $config->productplan->actionList;
  60. $storyCols['actions']['menu'] = array('unlinkStory');
  61. $storyCols['actions']['minWidth'] = 60;
  62. $bugCols['assignedTo']['type'] = 'user';
  63. $bugCols['actions']['list'] = $config->productplan->actionList;
  64. $bugCols['actions']['menu'] = array('unlinkBug');
  65. $bugCols['actions']['minWidth'] = 60;
  66. $canBeChanged = common::canBeChanged('plan', $plan);
  67. $canBatchUnlinkStory = common::hasPriv('productPlan', 'batchUnlinkStory');
  68. $canBatchCloseStory = common::hasPriv('story', 'batchClose');
  69. $canBatchEditStory = common::hasPriv('story', 'batchEdit');
  70. $canBatchReviewStory = common::hasPriv('story', 'batchReview');
  71. $canBatchChangeBranchStory = common::hasPriv('story', 'batchChangeBranch');
  72. $canBatchChangeModuleStory = common::hasPriv('story', 'batchChangeModule');
  73. $canBatchChangePlanStory = common::hasPriv('story', 'batchChangePlan');
  74. $canBatchChangeStageStory = common::hasPriv('story', 'batchChangeStage');
  75. $canBatchAssignToStory = common::hasPriv('story', 'batchAssignTo');
  76. $canBatchUnlinkBug = common::hasPriv('productPlan', 'batchUnlinkBug');
  77. $canBatchEditBug = common::hasPriv('bug', 'batchEdit');
  78. $canBatchChangePlanBug = common::hasPriv('bug', 'batchChangePlan');
  79. $canBatchActionStory = ($canBeChanged && ($canBatchUnlinkStory || $canBatchCloseStory || $canBatchEditStory || $canBatchReviewStory || $canBatchChangeBranchStory || $canBatchChangeModuleStory || $canBatchChangePlanStory || $canBatchChangeStageStory || $canBatchAssignToStory));
  80. $canBatchActionBug = ($canBeChanged && ($canBatchUnlinkBug || $canBatchEditBug || $canBatchChangePlanBug));
  81. unset($lang->story->reviewResultList[''], $lang->story->reviewResultList['revert']);
  82. unset($lang->story->reasonList[''], $lang->story->reasonList['subdivided'], $lang->story->reasonList['duplicate']);
  83. unset($plans[''], $lang->story->stageList[''], $users['']);
  84. $planItems = $moduleItems = $branchItems = $reviewRejectItems = $stageItems = $assignItems = $reviewResultItems = array();
  85. foreach($lang->story->reviewResultList as $key => $result) $reviewResultItems[$key] = array('text' => $result, 'class' => 'batch-btn', 'data-type' => 'story', 'data-url' => $this->createLink('story', 'batchReview', "result=$key"));
  86. foreach($lang->story->reasonList as $key => $reason) $reviewRejectItems[] = array('text' => $reason, 'class' => 'batch-btn', 'data-type' => 'story', 'data-url' => $this->createLink('story', 'batchReview', "result=reject&reason=$key"));
  87. foreach($branchTagOption as $branchID => $branchName) $branchItems[] = array('text' => $branchName, 'class' => 'batch-btn', 'data-type' => 'story', 'data-url' => $this->createLink('story', 'batchChangeBranch', "branchID=$branchID"));
  88. foreach($modules as $moduleID => $moduleName) $moduleItems[] = array('text' => $moduleName, 'class' => 'batch-btn', 'data-type' => 'story', 'data-url' => $this->createLink('story', 'batchChangeModule', "moduleID=$moduleID"));
  89. foreach($plans as $planID => $planName) $planItems[] = array('text' => $planName, 'class' => 'batch-btn', 'data-type' => 'story', 'data-url' => $this->createLink('story', 'batchChangePlan', "planID=$planID&oldPlanID={$plan->id}"));
  90. foreach($lang->story->stageList as $key => $stageName) $stageItems[] = array('text' => $stageName, 'class' => 'batch-btn', 'data-type' => 'story', 'data-url' => $this->createLink('story', 'batchChangeStage', "stage=$key"));
  91. $pinyinItems = common::convert2Pinyin($users);
  92. foreach($users as $account => $realname)
  93. {
  94. if($account == 'closed') continue;
  95. $assignItems[] = array('text' => $realname, 'keys' => zget($pinyinItems, $realname, ''), 'class' => 'batch-btn', 'data-type' => 'story', 'data-url' => $this->createLink('story', 'batchAssignTo', "productID=$plan->product"), 'data-account' => $account);
  96. }
  97. if(isset($reviewResultItems['reject'])) $reviewResultItems['reject'] = array('class' => 'not-hide-menu', 'text' => $lang->story->reviewResultList['reject'], 'items' => $reviewRejectItems);
  98. $reviewResultItems = array_values($reviewResultItems);
  99. $navStoryActionItems = array();
  100. if($canBatchCloseStory) $navStoryActionItems[] = array('text' => $lang->close, 'class' => 'batch-btn', 'data-type' => 'story', 'data-page' => 'batch', 'data-url' => helper::createLink('story', 'batchClose', "productID={$plan->product}"));
  101. if($canBatchEditStory) $navStoryActionItems[] = array('text' => $lang->edit, 'class' => 'batch-btn', 'data-type' => 'story', 'data-page' => 'batch', 'data-url' => helper::createLink('story', 'batchEdit', "productID=$plan->product&projectID=$projectID&branch=$branch"));
  102. if($canBatchReviewStory) $navStoryActionItems[] = array('class' => 'not-hide-menu', 'text' => $lang->story->review, 'items' => $reviewResultItems);
  103. if($canBatchChangeBranchStory && $product->type != 'normal') $navStoryActionItems[] = array('class' => 'not-hide-menu', 'text' => $lang->product->branchName[$product->type], 'items' => $branchItems);
  104. if($canBatchChangeModuleStory) $navStoryActionItems[] = array('class' => 'not-hide-menu', 'text' => $lang->story->moduleAB, 'items' => $moduleItems);
  105. if($canBatchChangePlanStory) $navStoryActionItems[] = array('class' => 'not-hide-menu', 'text' => $lang->story->planAB, 'items' => $planItems, 'listProps' => array('searchBox' => true, 'maxHeight' => 400));
  106. if($canBatchChangeStageStory) $navStoryActionItems[] = array('class' => 'not-hide-menu', 'text' => $lang->story->stageAB, 'items' => $stageItems);
  107. if($canBatchAssignToStory) $navStoryActionItems[] = array('class' => 'not-hide-menu', 'text' => $lang->story->assignedTo, 'items' => $assignItems, 'listProps' => array('searchBox' => true, 'maxHeight' => 400));
  108. $storyFootToolbar = array();
  109. if($canBatchActionStory)
  110. {
  111. $storyFootToolbar = array('items' => array
  112. (
  113. array('type' => 'btn-group', 'items' => array
  114. (
  115. array('text' => $lang->productplan->unlinkStoryAB, 'className' => 'batch-btn size-sm', 'disabled' => ($canBatchUnlinkStory ? '' : 'disabled'), 'btnType' => 'secondary', 'data-type' => 'story', 'data-url' => helper::createLink('productplan', 'batchUnlinkStory', "planID=$plan->id&orderBy=$orderBy")),
  116. array('caret' => 'up', 'className' => 'size-sm', 'btnType' => 'secondary', 'data-toggle' => 'dropdown', 'data-placement' => 'top-start', 'items' => $navStoryActionItems)
  117. ))
  118. ));
  119. }
  120. $planItems = array();
  121. foreach($plans as $planID => $planName) $planItems[] = array('text' => $planName, 'class' => 'batch-btn', 'data-type' => 'bug', 'data-url' => $this->createLink('bug', 'batchChangePlan', "planID=$planID"));
  122. $navBugActionItems = array();
  123. if($canBatchEditBug) $navBugActionItems[] = array('text' => $lang->edit, 'class' => 'batch-btn', 'data-type' => 'bug', 'data-page' => 'batch', 'data-url' => helper::createLink('bug', 'batchEdit', "productID=$plan->product&branch=$branch"));
  124. if($canBatchChangePlanBug) $navBugActionItems[] = array('class' => 'not-hide-menu', 'text' => $lang->story->planAB, 'items' => $planItems, 'listProps' => array('searchBox' => true, 'maxHeight' => 400));
  125. $bugFootToolbar = array();
  126. if($canBatchActionBug)
  127. {
  128. $bugFootToolbar = array('items' => array
  129. (
  130. array('type' => 'btn-group', 'items' => array
  131. (
  132. $canBatchUnlinkBug ? array('text' => $lang->productplan->unlinkAB, 'className' => 'batch-btn size-sm', 'btnType' => 'secondary', 'data-type' => 'bug', 'data-url' => helper::createLink('productplan', 'batchUnlinkBug', "planID=$plan->id&orderBy=$orderBy")) : null,
  133. array('caret' => 'up', 'className' => 'size-sm', 'btnType' => 'secondary', 'data-toggle' => 'dropdown', 'data-placement' => 'top-start', 'items' => $navBugActionItems)
  134. ))
  135. ));
  136. }
  137. $planStories = initTableData($planStories, $storyCols, $this->productplan);
  138. $planBugs = initTableData($planBugs, $bugCols, $this->productplan);
  139. foreach($planStories as $story) $story->estimate = helper::formatHours($story->estimate) . $config->hourUnit;
  140. $createStoryLink = common::hasPriv('story', 'create') ? $this->createLink('story', 'create', "productID=$plan->product&branch=$plan->branch&moduleID=0&storyID=0&projectID=$projectID&bugID=0&planID=$plan->id") : null;
  141. $batchCreateStoryLink = common::hasPriv('story', 'batchCreate') ? $this->createLink('story', 'batchCreate', "productID=$plan->product&branch=$plan->branch&moduleID=0&story=0&project=$projectID&plan={$plan->id}") : null;
  142. $createRequirementLink = common::hasPriv('requirement', 'create') ? $this->createLink('requirement', 'create', "productID=$plan->product&branch=$plan->branch&moduleID=0&storyID=0&projectID=$projectID&bugID=0&planID=$plan->id") : null;
  143. $batchCreateRequirementLink = common::hasPriv('requirement', 'batchCreate') ? $this->createLink('requirement', 'batchCreate', "productID=$plan->product&branch=$plan->branch&moduleID=0&story=0&project=$projectID&plan={$plan->id}") : null;
  144. $createEpicLink = common::hasPriv('epic', 'create') ? $this->createLink('epic', 'create', "productID=$plan->product&branch=$plan->branch&moduleID=0&storyID=0&projectID=$projectID&bugID=0&planID=$plan->id") : null;
  145. $batchCreateEpicLink = common::hasPriv('epic', 'batchCreate') ? $this->createLink('epic', 'batchCreate', "productID=$plan->product&branch=$plan->branch&moduleID=0&story=0&project=$projectID&plan={$plan->id}") : null;
  146. $branchNames = '';
  147. if($product->type != 'normal')
  148. {
  149. foreach(explode(',', (string)$plan->branch) as $branchID) $branchNames .= "{$branchOption[$branchID]},";
  150. $branchNames = trim($branchNames, ',');
  151. }
  152. $fnGetChildrenPlans = function($childrenPlans)
  153. {
  154. $childrenPlanItems = array();
  155. foreach($childrenPlans as $childrenPlan)
  156. {
  157. $childrenPlanItems[] = a(set::href(inlink('view', "planID={$childrenPlan->id}")), "#{$childrenPlan->id} {$childrenPlan->title}");
  158. $childrenPlanItems[] = h::br();
  159. }
  160. array_pop($childrenPlanItems);
  161. return $childrenPlanItems;
  162. };
  163. $actions = $this->loadModel('common')->buildOperateMenu($plan);
  164. foreach($actions as $actionType => $typeActions)
  165. {
  166. foreach($typeActions as $key => $action)
  167. {
  168. $actions[$actionType][$key]['url'] = str_replace(array('{id}', '{product}', '{branch}'), array((string)$plan->id, $plan->product, $plan->branch), $action['url']);
  169. $actions[$actionType][$key]['className'] = isset($action['className']) ? $action['className'] . ' ghost' : 'ghost';
  170. $actions[$actionType][$key]['iconClass'] = isset($action['iconClass']) ? $action['iconClass'] . ' text-primary' : 'text-primary';
  171. if($actionType == 'suffixActions')
  172. {
  173. if($action['icon'] == 'edit') $actions['suffixActions'][$key]['text'] = $lang->edit;
  174. if($action['icon'] == 'trash') $actions['suffixActions'][$key]['text'] = $lang->delete;
  175. }
  176. }
  177. }
  178. detailHeader
  179. (
  180. to::prefix
  181. (
  182. $isInModal ? null : backBtn(set::icon('back'), set::type('secondary'), set::url($this->session->productPlanList), $lang->goback),
  183. entityLabel(set(array('entityID' => $plan->id, 'level' => 1, 'text' => $plan->title))),
  184. span(setClass('label circle primary'), ($plan->begin == FUTURE_TIME || $plan->end == FUTURE_TIME) ? $lang->productplan->future : $plan->begin . '~' . $plan->end),
  185. $plan->deleted ? span(setClass('label danger'), $lang->product->deleted) : null
  186. ),
  187. !$isInModal && !$plan->deleted && $actions ? to::suffix
  188. (
  189. btnGroup(set::items($actions['mainActions'])),
  190. !empty($actions['mainActions']) && !empty($actions['suffixActions']) ? div(setClass('divider mx-2')): null,
  191. btnGroup(set::items($actions['suffixActions']))
  192. ) : null
  193. );
  194. detailBody
  195. (
  196. set::hasExtraMain(false),
  197. sectionList
  198. (
  199. tabs
  200. (
  201. setClass('w-full relative'),
  202. on::shown('.tab-pane')->call('$.apps.updateAppUrl', jsRaw('$this.data("url")')),
  203. tabPane
  204. (
  205. to::prefix(icon($lang->icons['story'], setClass('text-secondary'))),
  206. set::key('stories'),
  207. set::title($lang->productplan->linkedStories),
  208. set::active($type == 'story'),
  209. setData('url', sprintf($tabUrl, 'story')),
  210. $plan->parent >= 0 ? toolbar
  211. (
  212. setClass('tab-actions absolute right-0 gap-2'),
  213. setStyle('top', '-8px'),
  214. $isInModal || (empty($createStoryLink) && empty($batchCreateStoryLink)) ? null : dropdown
  215. (
  216. btn
  217. (
  218. $app->tab == 'project' ? setData('app', 'project') : null,
  219. set::text($lang->story->create),
  220. setClass('open-url secondary' . (empty($createStoryLink) ? ' disabled' : '')),
  221. set::icon('plus'),
  222. set::caret(true),
  223. set::url($createStoryLink)
  224. ),
  225. set::items(array(
  226. $this->config->URAndSR ? array('text' => $lang->requirement->create, 'url' => $createRequirementLink, 'class' => empty($createRequirementLink) ? 'disabled' : '', 'data-app' => $app->tab) : null,
  227. $this->config->enableER ? array('text' => $lang->epic->create, 'url' => $createEpicLink, 'class' => empty($createEpicLink) ? 'disabled' : '', 'data-app' => $app->tab) : null,
  228. array('text' => $lang->story->batchCreate, 'items' => array(
  229. array('text' => $lang->SRCommon, 'url' => $batchCreateStoryLink, 'class' => empty($batchCreateStoryLink) ? 'disabled' : '', 'data-app' => $app->tab),
  230. $this->config->URAndSR ? array('text' => $lang->URCommon, 'url' => $batchCreateRequirementLink, 'class' => empty($batchCreateRequirementLink) ? 'disabled' : '', 'data-app' => $app->tab) : null,
  231. $this->config->enableER ? array('text' => $lang->ERCommon, 'url' => $batchCreateEpicLink, 'class' => empty($batchCreateEpicLink) ? 'disabled' : '', 'data-app' => $app->tab) : null
  232. ))
  233. )),
  234. set::trigger('hover'),
  235. set::placement('bottom-end')
  236. ),
  237. !$isInModal && common::hasPriv('productplan', 'linkStory') ? btn
  238. (
  239. set::type('primary'),
  240. set::icon('link'),
  241. set::text($lang->productplan->linkStory),
  242. set::className('linkStory-btn'),
  243. bind::click('window.showLink', array('params' => array('story')))
  244. ) : null
  245. ) : toolbar(setClass('tab-actions absolute right-0 gap-2'), setStyle('top', '-8px')),
  246. dtable
  247. (
  248. setID('storyDTable'),
  249. set::plugins(array('sortable')),
  250. set::sortHandler('.move-plan'),
  251. set::onSortEnd(jsRaw('window.onSortEnd')),
  252. set::style(array('min-width' => '100%')),
  253. set::userMap($users),
  254. set::bordered(true),
  255. set::cols($storyCols),
  256. set::data(array_values($planStories)),
  257. set::noNestedCheck(),
  258. set::checkable($canBatchActionStory),
  259. set::onRenderCell(jsRaw('window.renderStoryCell')),
  260. set::footToolbar($storyFootToolbar),
  261. set::sortLink(createLink('productplan', 'view', "planID={$plan->id}&type=story&orderBy={name}_{sortType}&link=false&param={$param}&recTotal={$storyPager->recTotal}&recPerPage={$storyPager->recPerPage}&page={$storyPager->pageID}")),
  262. set::orderBy($orderBy),
  263. set::extraHeight('+144'),
  264. set::checkInfo(jsRaw("function(checkedIDList){return window.setStatistics(this, checkedIDList, '{$summary}');}")),
  265. set::footPager
  266. (
  267. usePager('storyPager', '', array(
  268. 'recPerPage' => $storyPager->recPerPage,
  269. 'recTotal' => $storyPager->recTotal,
  270. 'linkCreator' => helper::createLink('productplan', 'view', "planID={$plan->id}&type=story&orderBy={$orderBy}&link=false&param={$param}&recTotal={$storyPager->recTotal}&recPerPage={recPerPage}&page={page}")
  271. ))
  272. )
  273. )
  274. ),
  275. tabPane
  276. (
  277. to::prefix(icon($lang->icons['bug'], setClass('text-danger'))),
  278. set::key('bugs'),
  279. set::title($lang->productplan->linkedBugs),
  280. set::active($type == 'bug'),
  281. setData('url', sprintf($tabUrl, 'bug')),
  282. !$isInModal && $plan->parent >= 0 && common::hasPriv('productplan', 'linkBug') ? toolbar
  283. (
  284. setClass('tab-actions absolute right-0 gap-2'),
  285. setStyle('top', '-8px'),
  286. btn
  287. (
  288. set::type('primary'),
  289. set::icon('link'),
  290. set::text($lang->productplan->linkBug),
  291. set::className('linkBug-btn'),
  292. bind::click('window.showLink', array('params' => array('bug')))
  293. )
  294. ) : null,
  295. dtable
  296. (
  297. setID('bugDTable'),
  298. set::style(array('min-width' => '100%')),
  299. set::userMap($users),
  300. set::bordered(true),
  301. set::cols($bugCols),
  302. set::data(array_values($planBugs)),
  303. set::checkable($canBatchActionBug),
  304. set::footToolbar($bugFootToolbar),
  305. set::sortLink(createLink('productplan', 'view', "planID={$plan->id}&type=bug&orderBy={name}_{sortType}&link=false&param={$param}&recTotal={$bugPager->recTotal}&recPerPage={$bugPager->recPerPage}&page={$bugPager->pageID}")),
  306. set::orderBy($orderBy),
  307. set::extraHeight('+144'),
  308. set::footer(array('checkbox', 'toolbar', array('html' => sprintf($lang->productplan->bugSummary, count($planBugs)), 'className' => "text-dark"), 'flex', 'pager')),
  309. set::footPager
  310. (
  311. usePager('bugPager', '', array(
  312. 'recPerPage' => $bugPager->recPerPage,
  313. 'recTotal' => $bugPager->recTotal,
  314. 'linkCreator' => helper::createLink('productplan', 'view', "planID={$plan->id}&type=bug&orderBy={$orderBy}&link=false&param={$param}&recTotal={$bugPager->recTotal}&recPerPage={recPerPage}&page={page}")
  315. ))
  316. )
  317. )
  318. ),
  319. tabPane
  320. (
  321. to::prefix(icon('flag', setClass('text-special'))),
  322. set::key('planInfo'),
  323. set::title($lang->productplan->view),
  324. set::active($type == 'planInfo'),
  325. setData('url', sprintf($tabUrl, 'planInfo')),
  326. tableData
  327. (
  328. set::title($lang->productplan->basicInfo),
  329. item(set::name($lang->productplan->title), $plan->title),
  330. $plan->parent > 0 ? item(set::name($lang->productplan->parent), a(set::href(inlink('view', "planID={$parentPlan->id}")), "#{$parentPlan->id} {$parentPlan->title}")) : null,
  331. $product->type != 'normal' ? item(set::name($lang->product->branch), $branchNames) : null,
  332. item(set::name($lang->productplan->begin), $plan->begin == FUTURE_TIME ? $lang->productplan->future : $plan->begin),
  333. item(set::name($lang->productplan->end), $plan->end == FUTURE_TIME ? $lang->productplan->future : $plan->end),
  334. $plan->parent == '-1' ? item(set::name($lang->productplan->children), $fnGetChildrenPlans($childrenPlans)) : null,
  335. item(set::name($lang->productplan->status), $lang->productplan->statusList[$plan->status]),
  336. item(set::name($lang->productplan->desc), empty($plan->desc) ? $lang->noData : html(($plan->desc)))
  337. ),
  338. html($this->printExtendFields($plan, 'html', 'position=all', false)),
  339. h::hr(setClass('mt-4')),
  340. history(set::objectID($plan->id), set::commentBtn(false), set::objectType('productplan'))
  341. )
  342. )
  343. )
  344. );
  345. render();