ipd.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. <?php
  2. /**
  3. * 追加评审点列表到执行列表。
  4. * Append points to execution list.
  5. *
  6. * @param array $points
  7. * @param object $execution
  8. * @param array $rows
  9. * @param array $pendingReviews
  10. * @access public
  11. * @return array
  12. */
  13. public function appendPoints($points, $execution, $rows, $pendingReviews = array())
  14. {
  15. if(!$this->cookie->showStage) return $rows;
  16. $this->app->loadConfig('project');
  17. $this->loadModel('review');
  18. $this->loadModel('approval');
  19. foreach($points as $point)
  20. {
  21. $canSubmit = true;
  22. foreach($this->config->project->execution->dtable->actionsRule['point'] as $actionName)
  23. {
  24. $actions = explode('|', $actionName);
  25. foreach($actions as $name)
  26. {
  27. $rawAction = str_replace('Review', '', $name);
  28. $action = array('name' => $name);
  29. if($rawAction == 'progress' && !common::hasPriv('approval', 'progress')) continue;
  30. elseif($rawAction != 'progress' && !common::hasPriv('review', $rawAction)) continue;
  31. $clickable = $this->review->isClickable($point, $rawAction);
  32. $disabled = $clickable ? false : true;
  33. /* 阶段未开始时,当前评审点不允许评审。*/
  34. if($rawAction == 'create' && $point->execStatus == 'wait')
  35. {
  36. $disabled = true;
  37. $action['hint'] = $this->lang->review->stageNotStartTip;
  38. }
  39. /* 前一评审点未评审通过时,当前评审点不允许评审。*/
  40. if(!$point->isFirst && $rawAction == 'create' && $point->preResult != 'pass' && !$point->result)
  41. {
  42. $disabled = true;
  43. $action['hint'] = $this->lang->review->prePointNotPassTip;
  44. }
  45. if(!empty($execution->isTpl) && $rawAction == 'create') $disabled = true;
  46. if($point->result == 'pass' && $rawAction == 'create') $disabled = true;
  47. if($rawAction == 'recall' && $point->status != 'reverting' && $this->approval->canCancel($point)) $disabled = false;
  48. if($rawAction == 'assess') $disabled = !isset($pendingReviews[$point->review]);
  49. if($rawAction == 'create' && $disabled) $canSubmit = false;
  50. $action['disabled'] = $disabled;
  51. if(!$disabled) break;
  52. }
  53. if($action) $point->actions[] = $action;
  54. }
  55. $point->name = (common::hasPriv('review', 'view') and $point->rawStatus) ? html::a(helper::createLink('review', 'view', "id=$point->review"), $point->text, '', "", "data-app='project'") : "<span>$point->text</span>";
  56. if(!$point->rawStatus && $canSubmit) $point->name .= html::a(helper::createLink('review', 'create', "projectID=$point->project&deliverable=0&reviewID=0&type=decision&objectID={$point->category}"), '<i class="icon-confirm"></i></button>', '', 'class="btn btn-link submitBtn" title="' . $this->lang->programplan->submit . '"');
  57. $point->rawID = $point->id;
  58. $point->id = 'tid' . (string)$point->id;
  59. $point->end = $point->deadline;
  60. $point->left = '';
  61. $point->consumed = '';
  62. $point->estimate = '';
  63. $point->begin = '';
  64. $point->parent = 'pid' . (string)$point->parent;
  65. $rows[] = $point;
  66. }
  67. return $rows;
  68. }
  69. /**
  70. * Check ipd stage is ready to begin.
  71. *
  72. * @param array $stages
  73. * @access public
  74. * @return array
  75. */
  76. public function appendActions($stages = array())
  77. {
  78. if(empty($stages)) return $stages;
  79. $projects = $this->loadModel('project')->getPairsByModel('ipd');
  80. $newStages = array();
  81. $ipdProjectGroup = array();
  82. foreach($stages as $stage)
  83. {
  84. $stageProject = $stage->project;
  85. $newStages[$stage->id] = $stage;
  86. if($stage->parallel) continue; // 如果是并行阶段,跳过
  87. if(!isset($projects[$stageProject])) continue; // 不是IPD项目,跳过
  88. if($stage->parent != $stage->project) continue; // 不是父阶段,跳过
  89. $ipdProjectGroup[$stageProject][$stage->order] = $stage;
  90. }
  91. if(empty($ipdProjectGroup)) return $stages; // Do not need to append actions (no $ipdProjectGroup)
  92. foreach($ipdProjectGroup as $project => $projectStages)
  93. {
  94. $preStage = '';
  95. foreach($projectStages as $order => $stage)
  96. {
  97. $preStageName = $preStage ? $preStage->name : '';
  98. $stage->action = new stdclass();
  99. $stage->action->start = array('disabled' => false, 'hint' => '', 'preStageName' => $preStageName, 'stageName' => $stage->name);
  100. $stage->action->close = $stage->action->start;
  101. /* 上一阶段结束,当前阶段才能开始。*/
  102. if($preStage && $preStage->status != 'closed')
  103. {
  104. $message = sprintf($this->lang->execution->disabledTip->startTip, $preStage->name , $stage->name);
  105. $stage->action->start['disabled'] = true;
  106. $stage->action->start['hint'] = $message;
  107. }
  108. /* 当前阶段评审点都通过,当前阶段才能关闭。*/
  109. if(empty($stage->points)) continue;
  110. foreach($stage->points as $point)
  111. {
  112. if($point->result == 'pass') continue;
  113. $message = $this->lang->execution->disabledTip->closeTip;
  114. $stage->action->close['disabled'] = true;
  115. $stage->action->close['hint'] = $message;
  116. }
  117. $preStage = $stage;
  118. $newStages[$stage->id] = $stage;
  119. }
  120. }
  121. return $newStages;
  122. }
  123. /**
  124. * 获取执行列表数据。
  125. * Get statData.
  126. *
  127. * @param int $projectID
  128. * @param string $browseType
  129. * @param int $productID
  130. * @param int $branch
  131. * @param bool $withTasks
  132. * @param string|int $param
  133. * @param string $orderBy
  134. * @param object|null $pager
  135. * @access public
  136. * @return array
  137. */
  138. public function getStatData($projectID = 0, $browseType = 'undone', $productID = 0, $branch = 0, $withTasks = false, $param = '', $orderBy = 'order_asc', $pager = null)
  139. {
  140. $executions = parent::getStatData($projectID, $browseType, $productID, $branch, $withTasks, $param, $orderBy, $pager);
  141. $project = $this->loadModel('project')->getByID($projectID);
  142. if(!$this->app->tab == 'project' || ($project && $project->model != 'ipd')) return $executions;
  143. $points = $this->loadModel('review')->getPointsByProjectID($projectID);
  144. $executions = $this->buildExecutionTree($executions, $points);
  145. $executions = $this->appendActions($executions);
  146. return $executions;
  147. }
  148. /**
  149. * 构建执行树。
  150. * Build execution tree.
  151. *
  152. * @param array $executions
  153. * @param array $points
  154. *
  155. * @access public
  156. * @return array
  157. */
  158. public function buildExecutionTree($executions = array(), $points = array())
  159. {
  160. $prePointResult = '';
  161. $isFirst = true;
  162. $reviewDeadline = array();
  163. $this->loadModel('review');
  164. $this->loadModel('programplan');
  165. foreach($executions as $execution)
  166. {
  167. if($execution->projectModel == 'ipd' && $execution->grade == 1) $execution->isIpdStage = true;
  168. if($execution->grade > 1) continue; // 如果是子阶段,跳过
  169. $reviewDeadline[$execution->id]['stageEnd'] = $execution->end;
  170. $reviewDeadline[$execution->id]['stageBegin'] = $execution->begin;
  171. foreach($points as $id => $point)
  172. {
  173. if($point->execution != $execution->id) continue;
  174. $data = new stdclass();
  175. $data->id = $execution->id . '-' . $point->categoryTitle . '-' . $point->id;
  176. $data->type = 'point';
  177. $data->text = "<i class='icon-seal'></i> " . $point->title;
  178. $data->name = $point->title;
  179. $data->rawName = $point->title;
  180. $data->open = true;
  181. $data->duration = 1;
  182. $data->parent = $execution->id;
  183. $data->review = $point->review;
  184. $data->result = $point->result;
  185. $data->approval = $point->approval;
  186. $data->project = $execution->project;
  187. $data->begin = $data->deadline = $data->endDate = $point->deadline;
  188. $data->realBegan = $point->realBegan;
  189. $data->realEnd = $point->lastReviewedDate;;
  190. $data->rawStatus = $point->status;
  191. $data->status = $point->status ? $point->status : $this->lang->programplan->wait;
  192. $data->preResult = $prePointResult;
  193. $data->execStatus = $execution->status;
  194. $data->isFirst = $isFirst;
  195. $data->deadline = $this->programplan->getPointEndDate($execution->id, $point, $reviewDeadline);
  196. $data->category = $point->category;
  197. if($data->status == 'wait') $data->status = zget($this->lang->review->statusList, $data->status);
  198. if($isFirst) $isFirst = false;
  199. $execution->points[] = $data;
  200. $execution->isIpdStage = true;
  201. $prePointResult = $point->result;
  202. }
  203. }
  204. return $executions;
  205. }
  206. /*
  207. * 构建任务操作按钮。
  208. * Build task actions.
  209. *
  210. * @param array $executions
  211. * @access public
  212. * @return array
  213. */
  214. /**
  215. * @param mixed[] $executions
  216. */
  217. public function buildTaskActions($executions = array())
  218. {
  219. foreach($executions as $execution)
  220. {
  221. if(empty($execution->tasks)) continue;
  222. if(empty($execution->action->start['disabled'])) continue;
  223. $startAction = $execution->action->start;
  224. foreach($execution->tasks as $task)
  225. {
  226. foreach($task->actions as $key => $action)
  227. {
  228. if(!in_array($action['name'], array('startTask', 'finishTask', 'recordWorkhour'))) continue;
  229. $action['disabled'] = true;
  230. if(isset($startAction['preStageName']) && $action['name'] == 'startTask') $action['hint'] = sprintf($this->lang->execution->disabledTip->taskStartTip, $startAction['preStageName'], $startAction['stageName']);
  231. if(isset($startAction['preStageName']) && $action['name'] == 'finishTask') $action['hint'] = sprintf($this->lang->execution->disabledTip->taskFinishTip, $startAction['preStageName'], $startAction['stageName']);
  232. if(isset($startAction['preStageName']) && $action['name'] == 'recordWorkhour') $action['hint'] = sprintf($this->lang->execution->disabledTip->taskRecordTip, $startAction['preStageName'], $startAction['stageName']);
  233. $task->actions[$key] = $action;
  234. }
  235. }
  236. }
  237. return $executions;
  238. }
  239. /**
  240. * 生成dtable的行数据。
  241. * Generate row for dtable.
  242. *
  243. * @param array $executions
  244. * @param array $users
  245. * @param array $avatarList
  246. * @access public
  247. * @return array
  248. */
  249. public function generateRow($executions, $users, $avatarList)
  250. {
  251. $executions = parent::generateRow($executions, $users, $avatarList);
  252. $pendingReviews = $this->loadModel('approval')->getPendingReviews('review');
  253. $startActionInfo = array();
  254. foreach($executions as $execution)
  255. {
  256. foreach($execution->actions as $key => $action)
  257. {
  258. $actionName = $action['name'];
  259. if($actionName != 'start' && $actionName != 'close') continue;
  260. /* 如果是子阶段的开始动作,则继承父阶段的开始动作。*/
  261. if($execution->grade > 1 && $actionName == 'start' && isset($startActionInfo[$execution->parent]))
  262. {
  263. if(!isset($execution->action)) $execution->action = new stdclass();
  264. $execution->action->start = $startActionInfo[$execution->parent];
  265. $execution->actions[$key] = $startActionInfo[$execution->parent];
  266. $startActionInfo[$execution->id] = $startActionInfo[$execution->parent];
  267. continue;
  268. }
  269. if(empty($execution->action->$actionName['disabled']))
  270. {
  271. /* 阶段关闭未被禁用时,如果阶段的开始动作被禁用,阶段的关闭动作也被禁用。 */
  272. if($actionName == 'close' && isset($execution->action->start) && !empty($execution->action->start['disabled']))
  273. {
  274. $execution->actions[$key]['disabled'] = true;
  275. $execution->action->close['disabled'] = true;
  276. $execution->actions[$key]['hint'] = '';
  277. $execution->action->close['hint'] = '';
  278. }
  279. continue;
  280. }
  281. $action['disabled'] = $execution->action->$actionName['disabled'];
  282. $action['hint'] = $execution->action->$actionName['hint'];
  283. if($actionName == 'start')
  284. {
  285. $startActionInfo[$execution->id] = $action;
  286. $execution->action->start = array_merge($action, $execution->action->start);
  287. }
  288. $execution->actions[$key] = $action;
  289. }
  290. if(!empty($execution->points)) $executions = $this->appendPoints($execution->points, $execution, $executions, $pendingReviews);
  291. }
  292. if($this->cookie->showTask) $executions = $this->buildTaskActions($executions);
  293. return $executions;
  294. }
  295. /*
  296. * 检查阶段能否开启或关闭。
  297. * Check ipd stage is ready to begin.
  298. *
  299. * @param int $executionID
  300. * @param string $option
  301. * @access public
  302. * @return array
  303. */
  304. /**
  305. * @param int $executionID
  306. * @param string $option
  307. */
  308. public function checkStageStatus($executionID = 0, $option = 'start')
  309. {
  310. $execution = $this->getById($executionID);
  311. $project = $this->loadModel('project')->getById($execution->project);
  312. if(isset($project->model) && $project->model == 'ipd')
  313. {
  314. $executions = $this->getStatData($execution->project, 'all');
  315. $execution = isset($executions[$executionID]) ? $executions[$executionID] : $execution;
  316. /* 子阶段的开始动作继承父阶段的开始动作。 */
  317. if(!isset($execution->action) && $execution->grade > 1)
  318. {
  319. $pathExecutions = explode(',', trim($execution->path, ','));
  320. $firstGradeExecutionID = isset($pathExecutions[1]) ? $pathExecutions[1] : 0;
  321. if($firstGradeExecutionID && !empty($executions[$firstGradeExecutionID]) && !empty($executions[$firstGradeExecutionID]->action) && !empty($executions[$firstGradeExecutionID]->action->start))
  322. {
  323. if(!isset($execution->action)) $execution->action = new stdclass();
  324. $execution->action->start = $executions[$firstGradeExecutionID]->action->start;
  325. if(!empty($execution->action->start['disabled']))
  326. {
  327. /* 如果父阶段的开始动作被禁用,子阶段的关闭动作也被禁用。 */
  328. if(!isset($execution->action->close)) $execution->action->close = new stdclass();
  329. $execution->action->close = $execution->action->start;
  330. }
  331. }
  332. }
  333. if(!empty($execution->action->$option['hint']))
  334. {
  335. return array('message' => $execution->action->$option['hint'], 'disabled' => true);
  336. }
  337. }
  338. return array('disabled' => false);
  339. }
  340. /**
  341. * 根据产品/执行等信息获取任务列表
  342. * Get tasks by product/execution etc.
  343. *
  344. * @param int $productID
  345. * @param int|array $executionID
  346. * @param array $executions
  347. * @param string $browseType
  348. * @param int $queryID
  349. * @param int $moduleID
  350. * @param string $sort
  351. * @param object $pager
  352. * @access public
  353. * @return array
  354. */
  355. public function getTasks($productID, $executionID, $executions, $browseType, $queryID, $moduleID, $sort, $pager = null)
  356. {
  357. $tasks = parent::getTasks($productID, $executionID, $executions, $browseType, $queryID, $moduleID, $sort, $pager);
  358. return $this->loadModel('story')->getAffectObject($tasks, 'task');
  359. }
  360. /**
  361. * 判断操作按钮是否可以点击。
  362. * Judge an action is clickable or not.
  363. *
  364. * @param object $execution
  365. * @param string $action
  366. * @access public
  367. * @return bool
  368. */
  369. public static function isClickable($execution, $action)
  370. {
  371. if($action != 'delete') return parent::isClickable($execution, $action);
  372. if($action == 'delete')
  373. {
  374. if((!empty($execution->isIpdStage) || (isset($execution->projectInfo) && $execution->projectInfo->model == 'ipd')) && $execution->grade == 1)
  375. {
  376. return false;
  377. }
  378. else
  379. {
  380. return parent::isClickable($execution, $action);
  381. }
  382. }
  383. }