model.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. <?php
  2. /**
  3. * The model file of stage module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Chunsheng Wang <chunsheng@cnezsoft.com>
  8. * @package stage
  9. * @version $Id: model.php 5079 2013-07-10 00:44:34Z chencongzhi520@gmail.com $
  10. * @link https://www.zentao.net
  11. */
  12. ?>
  13. <?php
  14. class stageModel extends model
  15. {
  16. /**
  17. * 创建一个阶段。
  18. * Create a stage.
  19. *
  20. * @param object $stage
  21. * @access public
  22. * @return int|bool
  23. */
  24. public function create($stage)
  25. {
  26. if(isset($this->config->setPercent) && $this->config->setPercent == 1)
  27. {
  28. $totalPercent = $this->getTotalPercent($stage->workflowGroup);
  29. if(!is_numeric($stage->percent))
  30. {
  31. dao::$errors['percent'] = $this->lang->stage->error->notNum;
  32. return false;
  33. }
  34. if(round($totalPercent + $stage->percent) > 100)
  35. {
  36. dao::$errors['percent'] = $this->lang->stage->error->percentOver;
  37. return false;
  38. }
  39. }
  40. $this->lang->error->unique = $this->lang->error->repeat;
  41. if(!empty($stage->percent)) $stage->percent = (float)$stage->percent;
  42. $this->dao->insert(TABLE_STAGE)
  43. ->data($stage)
  44. ->autoCheck()
  45. ->batchCheck($this->config->stage->create->requiredFields, 'notempty')
  46. ->checkIF(!empty($stage->name), 'name', 'unique', "`workflowGroup` = '$stage->workflowGroup' AND `deleted` = '0'")
  47. ->exec();
  48. $stageID = $this->dao->lastInsertID();
  49. $maxOrder = $this->dao->select('MAX(`order`) AS maxOrder')->from(TABLE_STAGE)->where('workflowGroup')->eq($stage->workflowGroup)->andWhere('deleted')->eq('0')->fetch('maxOrder');
  50. $this->dao->update(TABLE_STAGE)->set('order')->eq((int)$maxOrder + 1)->where('id')->eq($stageID)->exec();
  51. if(dao::isError()) return false;
  52. return $this->dao->lastInsertID();
  53. }
  54. /**
  55. * 批量创建阶段。
  56. * Batch create stages.
  57. *
  58. * @param int $groupID
  59. * @param array $stages
  60. * @access public
  61. * @return bool
  62. */
  63. public function batchCreate($groupID = 0, $stages = array())
  64. {
  65. $setPercent = (isset($this->config->setPercent) && $this->config->setPercent == 1) ? true : false;
  66. if($setPercent)
  67. {
  68. $oldTotalPercent = $this->getTotalPercent($groupID);
  69. $totalPercent = 0;
  70. foreach($stages as $stage)
  71. {
  72. if($totalPercent > 100) break;
  73. $totalPercent += (float)$stage->percent;
  74. }
  75. if(round($oldTotalPercent + $totalPercent) > 100)
  76. {
  77. dao::$errors['message'] = $this->lang->stage->error->percentOver;
  78. return false;
  79. }
  80. }
  81. $stageNames = $this->dao->select('id,name')->from(TABLE_STAGE)->where('workflowGroup')->eq($groupID)->andWhere('deleted')->eq('0')->fetchPairs('name');
  82. foreach($stages as $rowID => $stage)
  83. {
  84. if(in_array($stage->name, $stageNames))
  85. {
  86. dao::$errors["name[$rowID]"] = sprintf($this->lang->error->repeat, $this->lang->stage->name, $stage->name);
  87. return false;
  88. }
  89. $stageNames[] = $stage->name;
  90. }
  91. $maxOrder = $this->dao->select('MAX(`order`) AS maxOrder')->from(TABLE_STAGE)->where('workflowGroup')->eq($groupID)->andWhere('deleted')->eq('0')->fetch('maxOrder');
  92. $this->loadModel('action');
  93. $now = helper::now();
  94. foreach($stages as $rowID => $stage)
  95. {
  96. $stage->workflowGroup = $groupID;
  97. $stage->createdBy = $this->app->user->account;
  98. $stage->createdDate = $now;
  99. if(!empty($stage->percent)) $stage->percent = (float)$stage->percent;
  100. $this->dao->insert(TABLE_STAGE)->data($stage)->autoCheck()
  101. ->batchCheck($this->config->stage->create->requiredFields, 'notempty')
  102. ->exec();
  103. if(dao::isError())
  104. {
  105. foreach(dao::getError() as $field => $error) dao::$errors["{$field}[{$rowID}]"] = $error;
  106. return false;
  107. }
  108. $stageID = $this->dao->lastInsertID();
  109. $this->action->create('stage', $stageID, 'Opened');
  110. $stageOrder = empty($maxOrder) ? 1 : ++ $maxOrder;
  111. $this->dao->update(TABLE_STAGE)->set('order')->eq($stageOrder)->where('id')->eq($stageID)->exec();
  112. }
  113. return true;
  114. }
  115. /**
  116. * 编辑一个阶段。
  117. * Update a stage.
  118. *
  119. * @param int $stageID
  120. * @param object $stage
  121. * @access public
  122. * @return bool|array
  123. */
  124. public function update($stageID, $stage)
  125. {
  126. $oldStage = $this->dao->select('*')->from(TABLE_STAGE)->where('id')->eq((int)$stageID)->fetch();
  127. if(!$oldStage) return false;
  128. if(isset($this->config->setPercent) && $this->config->setPercent == 1)
  129. {
  130. $totalPercent = $this->getTotalPercent($oldStage->workflowGroup);
  131. if(round($totalPercent + (float)$stage->percent - (float)$oldStage->percent) > 100)
  132. {
  133. dao::$errors['percent'] = $this->lang->stage->error->percentOver;
  134. return false;
  135. }
  136. }
  137. $this->lang->error->unique = $this->lang->error->repeat;
  138. $this->dao->update(TABLE_STAGE)
  139. ->data($stage)
  140. ->autoCheck()
  141. ->batchCheck($this->config->stage->edit->requiredFields, 'notempty')
  142. ->checkIF(isset($stage->percent) && $stage->percent != '', 'percent', 'float')->where('id')->eq((int)$stageID)
  143. ->checkIF(!empty($stage->name), 'name', 'unique', "`id` != '$stageID' AND `workflowGroup` = '$oldStage->workflowGroup' AND `deleted` = '0'")
  144. ->exec();
  145. if(dao::isError()) return false;
  146. return common::createChanges($oldStage, $stage);
  147. }
  148. /**
  149. * 获取阶段列表信息。
  150. * Get stage list info.
  151. *
  152. * @param string $orderBy
  153. * @param int $projectID
  154. * @param int $groupID
  155. * @param int $grade
  156. * @access public
  157. * @return array
  158. */
  159. public function getStages($orderBy = 'order_desc', $projectID = 0, $groupID = 0, $grade = 0)
  160. {
  161. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getStages();
  162. if($projectID)
  163. {
  164. return $this->dao->select('`id`,name,type,percent,openedBy as createdBy,`begin` as createdDate,lastEditedBy as editedBy,`end` as editedDate,deleted')
  165. ->from(TABLE_EXECUTION)
  166. ->where('type')->in('sprint,stage,kanban')
  167. ->andWhere('deleted')->eq('0')
  168. ->andWhere('vision')->eq($this->config->vision)
  169. ->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->sprints)->fi()
  170. ->beginIF($grade)->andWhere('grade')->eq($grade)->fi()
  171. ->andWhere('project')->eq($projectID)
  172. ->orderBy($orderBy)
  173. ->fetchAll('id');
  174. }
  175. $stages = $this->dao->select('*')->from(TABLE_STAGE)
  176. ->where('deleted')->eq(0)
  177. ->andWhere('workflowGroup')->eq($groupID)
  178. ->beginIF($this->config->edition != 'ipd')->andWhere('projectType')->ne('ipd')->fi()
  179. ->andWhere('type')->ne('lifecycle')
  180. ->orderBy($orderBy)
  181. ->fetchAll('id');
  182. $stagePoints = $this->dao->select('*')->from(TABLE_DECISION)->where('stage')->in(array_keys($stages))->andWhere('deleted')->eq('0')->orderBy('order_asc')->fetchGroup('stage');
  183. $pointList = array();
  184. foreach($stagePoints as $stageID => $points)
  185. {
  186. if(!isset($pointList[$stageID])) $pointList[$stageID] = array();
  187. foreach($points as $point)
  188. {
  189. if(!isset($pointList[$stageID][$point->type])) $pointList[$stageID][$point->type] = array();
  190. $pointList[$stageID][$point->type][$point->id] = $point->title;
  191. }
  192. }
  193. foreach($stages as $stage)
  194. {
  195. $stage->TRpoint = isset($pointList[$stage->id]['TR']) ? implode(', ', $pointList[$stage->id]['TR']) : '';
  196. $stage->DCPpoint = isset($pointList[$stage->id]['DCP']) ? implode(', ', $pointList[$stage->id]['DCP']) : '';
  197. $stage->pointList = zget($pointList, $stage->id, array());
  198. }
  199. return $stages;
  200. }
  201. /**
  202. * 通过ID获取阶段信息。
  203. * Get stage info by ID.
  204. *
  205. * @param int $stageID
  206. * @access public
  207. * @return object|bool
  208. */
  209. public function getByID($stageID)
  210. {
  211. return $this->dao->select('*')->from(TABLE_STAGE)->where('deleted')->eq(0)->andWhere('id')->eq((int)$stageID)->fetch();
  212. }
  213. /**
  214. * 获取给定模型下的阶段总百分比。
  215. * Get total percent of the type.
  216. *
  217. * @param string $type waterfall|waterfallplus
  218. * @return float
  219. * @param int $groupID
  220. */
  221. public function getTotalPercent($groupID)
  222. {
  223. return (float)$this->dao->select('sum(percent) as total')->from(TABLE_STAGE)->where('deleted')->eq('0')->andWhere('workflowGroup')->eq($groupID)->fetch('total');
  224. }
  225. /**
  226. * 添加内置评审点。
  227. * Add the builtin point.
  228. *
  229. * @param int $groupID
  230. * @return void
  231. */
  232. public function addBuiltinPoint($groupID)
  233. {
  234. $workflowGroup = $this->dao->select('*')->from(TABLE_WORKFLOWGROUP)->where('id')->eq($groupID)->fetch();
  235. $this->app->loadConfig('project');
  236. $stageList = $this->dao->select('*')->from(TABLE_STAGE)->where('workflowGroup')->eq($groupID)->fetchAll('id');
  237. if(empty($stageList))
  238. {
  239. $stageTypeList = array();
  240. if($workflowGroup->projectModel == 'ipd')
  241. {
  242. if($workflowGroup->projectType == 'ipd') $stageTypeList = $this->config->project->categoryStages['IPD'];
  243. if($workflowGroup->projectType == 'tpd') $stageTypeList = $this->config->project->categoryStages['TPD'];
  244. if($workflowGroup->projectType == 'cbb') $stageTypeList = $this->config->project->categoryStages['CBB'];
  245. if(in_array($workflowGroup->projectType, array('cpdproduct', 'cpdproject'))) $stageTypeList = $this->config->project->categoryStages['CPD'];
  246. }
  247. if(in_array($workflowGroup->projectModel, array('waterfall', 'waterfallplus'))) $stageTypeList = $this->config->project->categoryStages['waterfall'];
  248. foreach($stageTypeList as $stageType)
  249. {
  250. $builtinStage = new stdClass();
  251. $builtinStage->workflowGroup = $groupID;
  252. $builtinStage->createdBy = 'system';
  253. $builtinStage->createdDate = helper::now();
  254. $builtinStage->name = in_array($workflowGroup->projectModel, array('waterfall', 'waterfallplus')) ? zget($this->lang->stage->typeList, $stageType, '') : zget($this->lang->stage->ipdTypeList, $stageType, '');
  255. $builtinStage->type = $stageType;
  256. $builtinStage->projectType = $workflowGroup->projectModel;
  257. $this->dao->insert(TABLE_STAGE)->data($builtinStage)->exec();
  258. $stageID = $this->dao->lastInsertID();
  259. $stageList[$stageID] = $builtinStage;
  260. }
  261. }
  262. if($workflowGroup->projectModel != 'ipd') return;
  263. $builtinPoints = $this->dao->select('*')->from(TABLE_DECISION)->where('workflowGroup')->eq($groupID)->andWhere('builtin')->eq('1')->fetchAll();
  264. if(!empty($builtinPoints)) return;
  265. $decision = new stdClass();
  266. $decision->builtin = '1';
  267. $decision->createdBy = 'system';
  268. $decision->createdDate = helper::now();
  269. $decision->workflowGroup = $groupID;
  270. $decisionFlow = new stdClass();
  271. $decisionFlow->flow = 1;
  272. $decisionFlow->objectType = 'decision';
  273. $decisionFlow->relatedBy = 'system';
  274. $decisionFlow->relatedDate = helper::now();
  275. foreach($stageList as $id => $stage)
  276. {
  277. foreach($this->config->stage->ipdReviewPoint->{$stage->type} as $index => $point)
  278. {
  279. $decision->stage = $id;
  280. $decision->order = $index + 1;
  281. $decision->title = $point;
  282. $decision->type = strpos($point, 'TR') !== false ? 'TR' : 'DCP';
  283. $decision->category = $point;
  284. $this->dao->insert(TABLE_DECISION)->data($decision)->exec();
  285. $decisionID = $this->dao->lastInsertID();
  286. $decisionFlow->root = $stage->workflowGroup;
  287. $decisionFlow->objectID = $decisionID;
  288. $this->dao->insert(TABLE_APPROVALFLOWOBJECT)->data($decisionFlow)->exec();
  289. }
  290. }
  291. }
  292. /**
  293. * 获取阶段的评审点。
  294. * Get points of stage.
  295. *
  296. * @param string $type TR|DCP
  297. * @param int $stageID
  298. * @access public
  299. * @return bool
  300. */
  301. public function getStagePoints($type, $stageID)
  302. {
  303. $stage = $this->dao->select('*')->from(TABLE_STAGE)->where('id')->eq($stageID)->fetch();
  304. return $this->dao->select('t1.*,t2.flow')->from(TABLE_DECISION)->alias('t1')
  305. ->leftJoin(TABLE_APPROVALFLOWOBJECT)->alias('t2')->on("t1.id = t2.objectID AND t2.objectType = 'decision'")
  306. ->where('t1.deleted')->eq('0')
  307. ->andWhere('t1.stage')->eq($stageID)
  308. ->andWhere('t1.type')->eq($type)
  309. ->andWhere('t2.root')->eq($stage->workflowGroup)
  310. ->orderBy('order_asc')
  311. ->fetchAll('id');
  312. }
  313. /**
  314. * 设置阶段的评审点。
  315. * Set point of stage.
  316. *
  317. * @param string $type TR|DCP
  318. * @param int $stageID
  319. * @param array $stageID
  320. * @access public
  321. * @return bool
  322. * @param mixed[] $points
  323. */
  324. public function setPoint($type, $stageID, $points)
  325. {
  326. $stage = $this->getByID($stageID);
  327. $oldStagePoints = $this->getStagePoints($type, $stageID);
  328. foreach($points as $index => $point)
  329. {
  330. if(!empty($point->id) && isset($oldStagePoints[$point->id]))
  331. {
  332. $oldPoint = $oldStagePoints[$point->id];
  333. if($oldPoint->title != $point->title || $oldPoint->order != $index)
  334. {
  335. $this->dao->update(TABLE_DECISION)
  336. ->set('title')->eq($point->title)
  337. ->set('order')->eq($index)
  338. ->set('editedBy')->eq($this->app->user->account)
  339. ->set('editedDate')->eq(helper::now())
  340. ->where('id')->eq($point->id)
  341. ->exec();
  342. if(dao::isError()) return false;
  343. }
  344. if($oldPoint->flow != $point->flow)
  345. {
  346. $this->dao->update(TABLE_APPROVALFLOWOBJECT)
  347. ->set('flow')->eq($point->flow)
  348. ->where('objectID')->eq($point->id)
  349. ->andWhere('objectType')->eq('decision')
  350. ->andWhere('root')->eq($stage->workflowGroup)
  351. ->exec();
  352. if(dao::isError()) return false;
  353. }
  354. unset($oldStagePoints[$point->id]);
  355. }
  356. else
  357. {
  358. $newPoint = new stdClass();
  359. $newPoint->workflowGroup = $stage->workflowGroup;
  360. $newPoint->stage = $stageID;
  361. $newPoint->title = $point->title;
  362. $newPoint->type = $type;
  363. $newPoint->builtin = '0';
  364. $newPoint->createdBy = $this->app->user->account;
  365. $newPoint->createdDate = helper::now();
  366. $newPoint->order = $index;
  367. $this->dao->insert(TABLE_DECISION)->data($newPoint)->exec();
  368. $newPointID = $this->dao->lastInsertID();
  369. if(dao::isError()) return false;
  370. $approvalFlowObject = new stdClass();
  371. $approvalFlowObject->root = $stage->workflowGroup;
  372. $approvalFlowObject->flow = $point->flow;
  373. $approvalFlowObject->objectType = 'decision';
  374. $approvalFlowObject->objectID = $newPointID;
  375. $approvalFlowObject->relatedBy = $this->app->user->account;
  376. $approvalFlowObject->relatedDate = helper::now();
  377. $approvalFlowObject->extra = $type;
  378. $this->dao->insert(TABLE_APPROVALFLOWOBJECT)->data($approvalFlowObject)->exec();
  379. if(dao::isError()) return false;
  380. }
  381. }
  382. if(!empty($oldStagePoints))
  383. {
  384. $this->dao->update(TABLE_DECISION)->set('deleted')->eq('1')->where('id')->in(array_keys($oldStagePoints))->exec();
  385. $approvalFlowObjectIDList = $this->dao->select('id')->from(TABLE_APPROVALFLOWOBJECT)
  386. ->where('objectID')->in(array_keys($oldStagePoints))
  387. ->andWhere('objectType')->eq('decision')
  388. ->andWhere('root')->eq($stage->workflowGroup)
  389. ->fetchPairs();
  390. $this->dao->delete()->from(TABLE_APPROVALFLOWOBJECT)->where('id')->in($approvalFlowObjectIDList)->exec(); // 删除评审流程
  391. $this->dao->delete()->from(TABLE_REVIEWCL)->where('object')->in($approvalFlowObjectIDList)->exec(); // 删除检查清单
  392. }
  393. return true;
  394. }
  395. /**
  396. * 更新排序。
  397. * Update order.
  398. *
  399. * @param array $sortedIdList
  400. * @access public
  401. * @return void
  402. */
  403. public function updateOrder($sortedIdList)
  404. {
  405. foreach($sortedIdList as $stageID => $order)
  406. {
  407. $this->dao->update(TABLE_STAGE)->set('order')->eq($order + 1)->where('id')->eq($stageID)->exec();
  408. }
  409. }
  410. }