zen.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. <?php
  2. class pivotZen extends pivot
  3. {
  4. /**
  5. * 获取分组里一个透视表的默认方法和参数。
  6. * Get default method name and parameters of a pivot in a group.
  7. *
  8. * @param int $dimension
  9. * @param int $group
  10. * @access protected
  11. * @return array
  12. * @param int $dimensionID
  13. * @param int $groupID
  14. */
  15. protected function getDefaultMethodAndParams($dimensionID, $groupID)
  16. {
  17. $currentGroup = $this->loadModel('tree')->getByID($groupID);
  18. if(empty($currentGroup) || $currentGroup->grade != 1) return array('', '');
  19. $groups = $this->pivot->getGroupsByDimensionAndPath($dimensionID, $currentGroup->path);
  20. if(!$groups) return array('', '');
  21. foreach($groups as $group)
  22. {
  23. if($this->config->edition == 'open' && $group->grade == 1) continue;
  24. $pivotID = $this->pivot->getPivotID($group->id);
  25. if($pivotID) return array('show', "groupID={$group->id}&pivotID={$pivotID}");
  26. }
  27. $firstDimension = $this->loadModel('dimension')->getFirst();
  28. if($dimensionID != $firstDimension->id) return array('', '');
  29. if(empty($this->lang->pivotList->{$currentGroup->collector}->lists)) return array('', '');
  30. foreach($this->lang->pivotList->{$currentGroup->collector}->lists as $item)
  31. {
  32. $items = explode('|', $item);
  33. if(count($items) != 3) continue;
  34. $method = $items[2];
  35. if(common::hasPriv('pivot', $method)) return array($method, '');
  36. }
  37. return array('', '');
  38. }
  39. /**
  40. * 获取菜单项。
  41. * Get menu items.
  42. *
  43. * @param array $menus
  44. * @access protected
  45. * @return array
  46. */
  47. protected function getMenuItems($menus)
  48. {
  49. $items = array();
  50. foreach($menus as $menu)
  51. {
  52. if(isset($menu->url)) $items[] = $menu;
  53. }
  54. return $items;
  55. }
  56. /**
  57. * 获取侧边栏菜单。
  58. * Get sidebar menus of pivot.
  59. *
  60. * @param int $dimensionID
  61. * @param int $groupID
  62. * @access protected
  63. * @return array
  64. */
  65. protected function getSidebarMenus($dimensionID, $groupID)
  66. {
  67. $currentGroup = $this->loadModel('tree')->getByID($groupID);
  68. if(empty($currentGroup) || $currentGroup->grade != 1) return array();
  69. $groups = $this->pivot->getGroupsByDimensionAndPath($dimensionID, $currentGroup->path);
  70. if(!$groups) return array();
  71. $this->loadModel('bi');
  72. /* $firstAction = $this->loadModel('action')->getAccountFirstAction($this->app->user->account);
  73. $builtins = array_column($this->config->bi->builtin->pivots, 'id', 'id');*/
  74. $menus = array();
  75. foreach($groups as $group)
  76. {
  77. if($this->config->edition == 'open' && $group->grade == 1) continue;
  78. $pivots = $this->pivot->getAllPivotByGroupID($group->id);
  79. $pivots = $this->pivot->filterInvisiblePivot($pivots);
  80. $pivots = $this->loadModel('mark')->getMarks($pivots, 'pivot', 'view');
  81. $pivots = $this->pivot->isVersionChange($pivots, false);
  82. if(empty($pivots)) continue;
  83. if($group->grade > 1) $menus[] = (object)array('id' => $group->id, 'parent' => 0, 'name' => $group->name);
  84. if($pivots) $pivots = $this->pivot->processPivot($pivots, false);
  85. foreach($pivots as $pivot)
  86. {
  87. /* $this->setNewMark($pivot, $firstAction, $builtins);*/
  88. $params = helper::safe64Encode("groupID={$group->id}&pivotID={$pivot->id}&mark=view");
  89. $url = inlink('preview', "dimension={$dimensionID}&group={$currentGroup->id}&method=show&params={$params}");
  90. $menus[] = (object)array('id' => $group->id . '_' . $pivot->id, 'parent' => $group->grade > 1 ? $group->id : 0, 'name' => $pivot->name, 'url' => $url);
  91. }
  92. }
  93. $firstDimension = $this->loadModel('dimension')->getFirst();
  94. if($dimensionID != $firstDimension->id) return $menus;
  95. $builtinMenus = $this->getBuiltinMenus($dimensionID, $currentGroup);
  96. return array_merge($menus, $builtinMenus);
  97. }
  98. /**
  99. * 设置 “新” 标签。
  100. * Set new mark.
  101. *
  102. * @param object $pivot
  103. * @param object $firstAction
  104. * @access protected
  105. * @return void
  106. * @param mixed[] $builtins
  107. */
  108. protected function setNewMark($pivot, $firstAction, $builtins)
  109. {
  110. /* 如果不是内置透视表,则不需要展示“新”标签。*/
  111. /* If the pivot is not built-in, no need to display the "new" tag. */
  112. if($pivot->builtin == 0) return;
  113. /* 版本没有改变,此时讨论是不是新透视表。*/
  114. /* The version has not changed, so it is judged whether it is a new pivot. */
  115. if(!$pivot->versionChange)
  116. {
  117. if(!isset($builtins[$pivot->id])) return;
  118. if(!$pivot->mark && $pivot->createdDate < $firstAction->date) $pivot->mark = true;
  119. $isMainVersion = filter_var($pivot->version, FILTER_VALIDATE_INT) !== false;
  120. if(!$pivot->mark && $isMainVersion) $pivot->name = array('text' => $pivot->name, 'html' => $pivot->name . ' <span class="label ghost size-sm bg-secondary-50 text-secondary-500 rounded-full">' . $this->lang->pivot->new . '</span>');
  121. }
  122. else
  123. {
  124. $maxVersion = $this->pivot->getMaxVersion($pivot->id);
  125. if(!$this->loadModel('mark')->isMark('pivot', $pivot->id, $maxVersion, 'view'))
  126. {
  127. $pivot->name = array('text' => $pivot->name, 'html' => $pivot->name . ' <span class="label ghost size-sm bg-secondary-50 text-secondary-500 rounded-full">' . $this->lang->pivot->newVersion . '</span>');
  128. }
  129. }
  130. }
  131. /**
  132. * 在第一个维度上显示内置透视表。
  133. * Display the built-in pivots in the first dimension.
  134. *
  135. * @param int $dimensionID
  136. * @param object $currengGroup
  137. * @access protected
  138. * @return array
  139. * @param object $currentGroup
  140. */
  141. protected function getBuiltinMenus($dimensionID, $currentGroup)
  142. {
  143. $collector = $currentGroup->collector;
  144. if(empty($this->lang->pivotList->$collector->lists)) return array();
  145. $menus = array();
  146. ksort($this->lang->pivotList->$collector->lists);
  147. foreach($this->lang->pivotList->$collector->lists as $item)
  148. {
  149. $items = explode('|', $item);
  150. if(count($items) != 3) continue;
  151. $label = $items[0];
  152. $method = $items[2];
  153. if(!common::hasPriv('pivot', $method)) continue;
  154. $url = inlink('preview', "dimension={$dimensionID}&group={$currentGroup->id}&method={$method}");
  155. $menus[] = (object)array('id' => $method, 'parent' => 0, 'name' => $label, 'url' => $url);
  156. }
  157. return $menus;
  158. }
  159. /**
  160. * Preview pivots of a group.
  161. *
  162. * @param int $groupID
  163. * @param int $pivotID
  164. * @param string $mark
  165. * @param string|null $version
  166. * @access public
  167. * @return void
  168. */
  169. public function show($groupID, $pivotID, $mark = '', $version = null)
  170. {
  171. $this->pivot->checkAccess($pivotID, 'preview');
  172. if(is_null($version))
  173. {
  174. $pivot = $this->pivot->getByID($pivotID, true);
  175. }
  176. else
  177. {
  178. $pivot = $this->pivot->getPivotSpec($pivotID, $version, true);
  179. }
  180. $driver = $pivot->driver;
  181. $this->pivot->isVersionChange($pivot);
  182. if($mark && $pivot->builtin == 1)
  183. {
  184. $markVersion = $pivot->versionChange ? $this->pivot->getMaxVersion($pivot->id) : $pivot->version;
  185. if(!$this->loadModel('mark')->isMark('pivot', $pivot->id, $markVersion, 'view'))
  186. {
  187. $this->loadModel('mark')->setMark(array($pivot->id), 'pivot', $markVersion, 'view');
  188. }
  189. }
  190. if(isset($_POST['filterValues']) and $_POST['filterValues'])
  191. {
  192. $filterValuesData = is_string($this->post->filterValues) ? explode(',', $this->post->filterValues) : $this->post->filterValues;
  193. foreach($filterValuesData as $key => $value) $pivot->filters[$key]['default'] = $value;
  194. }
  195. $showOrigin = false;
  196. if(isset($_POST['summary']) and $_POST['summary']) $showOrigin = $this->post->summary == 'notuse';
  197. list($sql, $filterFormat) = $this->pivot->getFilterFormat($pivot->sql, $pivot->filters);
  198. $fields = json_decode(json_encode($pivot->fieldSettings), true);
  199. $langs = json_decode($pivot->langs, true) ?? array();
  200. $settingShowOrigin = isset($pivot->settings['summary']) && $pivot->settings['summary'] == 'notuse';
  201. if($showOrigin || $settingShowOrigin)
  202. {
  203. list($data, $configs) = $this->pivot->genOriginSheet($fields, $pivot->settings, $sql, $filterFormat, $langs, $driver);
  204. }
  205. else
  206. {
  207. list($data, $configs) = $this->pivot->genSheet($fields, $pivot->settings, $sql, $filterFormat, $langs, $driver);
  208. }
  209. $this->view->hasVersionMark = $this->loadModel('mark')->hasMark('pivot', $pivotID, 'all', 'version');
  210. $this->view->pivotName = $pivot->name;
  211. $this->view->title = $pivot->name;
  212. $this->view->currentMenu = $groupID . '_' . $pivot->id;
  213. $this->view->currentGroup = $groupID;
  214. $this->view->pivot = $pivot;
  215. $this->view->showOrigin = $showOrigin;
  216. $this->view->data = $data;
  217. $this->view->configs = $configs;
  218. }
  219. /**
  220. * Bug create pivot.
  221. *
  222. * @param string $begin
  223. * @param string $end
  224. * @param int $product
  225. * @param int $execution
  226. * @access public
  227. * @return void
  228. */
  229. public function bugCreate($begin = '', $end = '', $product = 0, $execution = 0)
  230. {
  231. $this->app->loadLang('bug');
  232. $begin = $begin ? date('Y-m-d', strtotime($begin)) : date('Y-m-01', strtotime('last month'));
  233. $end = date('Y-m-d', strtotime($end ?: 'now'));
  234. $this->view->title = $this->lang->pivot->bugCreate;
  235. $this->view->pivotName = $this->lang->pivot->bugCreate;
  236. $this->view->bugs = $this->pivot->getBugs($begin, $end, $product ? $product : 0, $execution ? $execution : 0);
  237. $this->view->users = $this->loadModel('user')->getPairs('noletter|noclosed');
  238. $this->view->executions = $this->pivot->getProjectExecutions();
  239. $this->view->products = $this->loadModel('product')->getPairs('', 0, '', 'all');
  240. $this->view->begin = $begin;
  241. $this->view->end = $end;
  242. $this->view->execution = $execution;
  243. $this->view->product = $product;
  244. $this->view->currentMenu = 'bugcreate';
  245. }
  246. /**
  247. * Bug assign pivot.
  248. *
  249. * @access public
  250. * @return void
  251. */
  252. public function bugAssign()
  253. {
  254. $this->session->set('productList', $this->app->getURI(true), 'product');
  255. $this->view->title = $this->lang->pivot->bugAssign;
  256. $this->view->pivotName = $this->lang->pivot->bugAssign;
  257. $this->view->bugs = $this->pivot->getBugAssign();
  258. $this->view->users = $this->loadModel('user')->getPairs('noletter|noclosed');
  259. $this->view->currentMenu = 'bugassign';
  260. }
  261. /**
  262. * Product information pivot.
  263. *
  264. * @params string $conditions
  265. * @access public
  266. * @return void
  267. * @param int|string $productID
  268. * @param string $conditions
  269. * @param string $productStatus
  270. * @param string $productType
  271. */
  272. public function productSummary($conditions = '', $productID = 0, $productStatus = 'normal', $productType = 'normal')
  273. {
  274. $this->app->loadLang('story');
  275. $this->app->loadLang('product');
  276. $this->app->loadLang('productplan');
  277. $this->session->set('productList', $this->app->getURI(true), 'product');
  278. $filters = array('productID' => $productID, 'productStatus' => $productStatus, 'productType' => $productType);
  279. $products = $this->pivot->getProducts($conditions, 'story', $filters);
  280. $this->view->filters = $filters;
  281. $this->view->title = $this->lang->pivot->productSummary;
  282. $this->view->pivotName = $this->lang->pivot->productSummary;
  283. $this->view->products = $this->processProductsForProductSummary($products);
  284. $this->view->users = $this->loadModel('user')->getPairs('noletter|noclosed');
  285. $this->view->conditions = $conditions;
  286. $this->view->currentMenu = 'productsummary';
  287. }
  288. /**
  289. * 把计划数组展开为独立的产品对象并添加跨行合并属性。
  290. * Expand the plans property to single product objects and add rowspan property.
  291. *
  292. * @params array $products
  293. * @access public
  294. * @return array
  295. * @param mixed[] $products
  296. */
  297. public function processProductsForProductSummary($products)
  298. {
  299. $productList = array();
  300. foreach($products as $product)
  301. {
  302. if(!isset($product->plans))
  303. {
  304. $product->planTitle = '';
  305. $product->planBegin = '';
  306. $product->planEnd = '';
  307. $product->storyDraft = 0;
  308. $product->storyReviewing = 0;
  309. $product->storyActive = 0;
  310. $product->storyChanging = 0;
  311. $product->storyClosed = 0;
  312. $product->storyTotal = 0;
  313. $productList[] = $product;
  314. continue;
  315. }
  316. $first = true;
  317. foreach($product->plans as $plan)
  318. {
  319. $newProduct = clone $product;
  320. $newProduct->planTitle = $plan->title;
  321. $newProduct->planBegin = $plan->begin == '2030-01-01' ? $this->lang->productplan->future : $plan->begin;
  322. $newProduct->planEnd = $plan->end == '2030-01-01' ? $this->lang->productplan->future : $plan->end;
  323. $newProduct->storyDraft = isset($plan->status['draft']) ? $plan->status['draft'] : 0;
  324. $newProduct->storyReviewing = isset($plan->status['reviewing']) ? $plan->status['reviewing'] : 0;
  325. $newProduct->storyActive = isset($plan->status['active']) ? $plan->status['active'] : 0;
  326. $newProduct->storyChanging = isset($plan->status['changing']) ? $plan->status['changing'] : 0;
  327. $newProduct->storyClosed = isset($plan->status['closed']) ? $plan->status['closed'] : 0;
  328. $newProduct->storyTotal = $newProduct->storyDraft + $newProduct->storyReviewing + $newProduct->storyActive + $newProduct->storyChanging + $newProduct->storyClosed;
  329. if($first) $newProduct->rowspan = count($newProduct->plans);
  330. $productList[] = $newProduct;
  331. $first = false;
  332. }
  333. }
  334. return $productList;
  335. }
  336. /**
  337. * Project deviation pivot.
  338. *
  339. * @param string $begin
  340. * @param string $end
  341. * @access public
  342. * @return void
  343. */
  344. public function projectDeviation($begin = '', $end = '')
  345. {
  346. $this->session->set('executionList', $this->app->getURI(true), 'execution');
  347. $begin = $begin ? date('Y-m-d', strtotime($begin)) : date('Y-m-01');
  348. $end = $end ? date('Y-m-d', strtotime($end)) : date('Y-m-d', strtotime(date('Y-m-01', strtotime('next month')) . ' -1 day'));
  349. $this->view->title = $this->lang->pivot->projectDeviation;
  350. $this->view->pivotName = $this->lang->pivot->projectDeviation;
  351. $this->view->executions = $this->pivot->getExecutions($begin, $end);
  352. $this->view->begin = $begin;
  353. $this->view->end = $end;
  354. $this->view->currentMenu = 'projectdeviation';
  355. }
  356. /**
  357. * 组织透视表。
  358. * Workload pivot.
  359. *
  360. * @param string $begin
  361. * @param string $end
  362. * @param int $days
  363. * @param float $workhour
  364. * @param int $dept
  365. * @param string $assign
  366. * @access public
  367. * @return void
  368. */
  369. public function workload($begin = '', $end = '', $days = 0, $workhour= 0, $dept = 0, $assign = 'assign')
  370. {
  371. $this->app->loadConfig('execution');
  372. $this->session->set('executionList', $this->app->getURI(true), 'execution');
  373. $begin = $begin ? strtotime($begin) : time();
  374. $end = $end ? strtotime($end) : time() + (7 * 24 * 3600);
  375. $end += 24 * 3600;
  376. $beginWeekDay = date('w', $begin);
  377. $begin = date('Y-m-d', $begin);
  378. $end = date('Y-m-d', $end);
  379. if(empty($workhour)) $workhour = $this->config->execution->defaultWorkhours;
  380. $diffDays = helper::diffDate($end, $begin);
  381. if($days > $diffDays) $days = $diffDays;
  382. if(empty($days))
  383. {
  384. $weekDay = $beginWeekDay;
  385. $days = $diffDays;
  386. for($i = 0; $i < $diffDays; $i++, $weekDay++)
  387. {
  388. $weekDay = $weekDay % 7;
  389. if(($this->config->execution->weekend == 2 && $weekDay == 6) || $weekDay == 0) $days--;
  390. }
  391. }
  392. $allHour = $workhour * $days;
  393. $users = $this->loadModel('user')->getPairs('noletter|noclosed');
  394. $this->view->title = $this->lang->pivot->workload;
  395. $this->view->pivotName = $this->lang->pivot->workload;
  396. $this->view->workload = $this->pivot->getWorkload($dept, $assign, $users, $allHour);
  397. $this->view->depts = $this->loadModel('dept')->getOptionMenu();
  398. $this->view->users = $users;
  399. $this->view->dept = $dept;
  400. $this->view->begin = $begin;
  401. $this->view->end = date('Y-m-d', strtotime($end) - 24 * 3600);
  402. $this->view->days = $days;
  403. $this->view->workhour = $workhour;
  404. $this->view->assign = $assign;
  405. $this->view->currentMenu = 'workload';
  406. }
  407. /**
  408. * 获取下钻列配置。
  409. * Get drill.
  410. *
  411. * @param int $pivotID
  412. * @param string $colName
  413. * @param string $status
  414. * @access public
  415. * @return object
  416. * @param string $version
  417. */
  418. public function getDrill($pivotID, $version, $colName, $status = 'published')
  419. {
  420. if($status == 'published')
  421. {
  422. $drills = $this->pivot->fetchPivotDrills($pivotID, $version, $colName);
  423. return reset($drills);
  424. }
  425. $cache = $this->getCache($pivotID);
  426. $drills = json_decode(json_encode($cache->drills), true);
  427. foreach($drills as $drill)
  428. {
  429. $drill = (object)$drill;
  430. $drill->condition = (array)$drill->condition;
  431. if($drill->field == $colName) return $drill;
  432. }
  433. return new stdclass();
  434. }
  435. /**
  436. * 获取筛选器的下拉选项 URL。
  437. * Get filter options url.
  438. *
  439. * @param array $filter
  440. * @access public
  441. * @return string
  442. * @param string $sql
  443. * @param mixed[] $fieldSettings
  444. */
  445. public function getFilterOptionUrl($filter, $sql = '', $fieldSettings = array())
  446. {
  447. $field = $filter['field'];
  448. $from = zget($filter, 'from', 'result');
  449. $value = zget($filter, 'default', '');
  450. $values = is_array($value) ? implode(',', $value) : $value;
  451. $url = helper::createLink('pivot', 'ajaxGetSysOptions', "search={search:base64}");
  452. $data = array();
  453. $data['values'] = $values;
  454. if($from == 'query')
  455. {
  456. $data['type'] = $filter['typeOption'];
  457. }
  458. else
  459. {
  460. $fieldSetting = $fieldSettings[$field];
  461. $fieldSetting = (array)$fieldSetting;
  462. $fieldType = $fieldSetting['type'];
  463. $data['type'] = $fieldType;
  464. $data['object'] = $fieldSetting['object'];
  465. $data['field'] = $fieldType != 'options' && $fieldType != 'object' ? $field : $fieldSetting['field'];
  466. $data['saveAs'] = zget($filter, 'saveAs', $field);
  467. $data['sql'] = $sql;
  468. $data['originalField'] = zget($fieldSetting, 'field', $data['field']);
  469. }
  470. return (object)array('url' => $url, 'method' => 'post', 'data' => $data);
  471. }
  472. }