model.php 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470
  1. <?php
  2. /**
  3. * The model file of program module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(https://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 program
  9. * @link https://www.zentao.net
  10. */
  11. class programModel extends model
  12. {
  13. /**
  14. * 提示权限不足并跳转页面。
  15. * Show accessDenied response.
  16. *
  17. * @access public
  18. * @return void
  19. */
  20. public function accessDenied()
  21. {
  22. if(commonModel::isTutorialMode()) return true;
  23. $link = helper::createLink('program', 'browse');
  24. if(!$this->server->http_referer) $link = helper::createLink('my', 'index');
  25. $loginLink = $this->config->requestType == 'GET' ? "?{$this->config->moduleVar}=user&{$this->config->methodVar}=login" : "user{$this->config->requestFix}login";
  26. if(strpos($this->server->http_referer, $loginLink) !== false) $link = helper::createLink('my', 'index');
  27. return $this->app->control->sendError($this->lang->program->accessDenied, $link);
  28. }
  29. /**
  30. * 设置并返回一个用户可见的项目集ID。
  31. * Set and return the projects that user can see.
  32. *
  33. * @param int $programID
  34. * @param array $programs
  35. * @access public
  36. * @return int
  37. */
  38. public function checkAccess($programID = 0, $programs = array())
  39. {
  40. if($programID > 0) $this->session->set('program', $programID, $this->app->tab);
  41. if(!$programID && $this->cookie->lastProgram) $this->session->set('program', $this->cookie->lastProgram, $this->app->tab);
  42. if(!$programID && !$this->session->program) $this->session->set('program', key($programs), $this->app->tab);
  43. if(!isset($programs[$this->session->program]))
  44. {
  45. $this->session->set('program', key($programs), $this->app->tab);
  46. if($programID && strpos(",{$this->app->user->view->programs},", ",{$this->session->program},") === false) $this->accessDenied();
  47. }
  48. return (int)$this->session->program;
  49. }
  50. /**
  51. * 获取项目集id:name的键值对。
  52. * Gets the pair for the program id:name
  53. *
  54. * @param bool $isQueryAll
  55. * @param string $orderBy
  56. * @access public
  57. * @return array
  58. */
  59. public function getPairs($isQueryAll = false, $orderBy = 'id_desc')
  60. {
  61. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getProgramPairs();
  62. return $this->dao->select('id, name')->from(TABLE_PROGRAM)
  63. ->where('type')->eq('program')
  64. ->andWhere('deleted')->eq(0)
  65. ->beginIF(!$this->app->user->admin && !$isQueryAll)->andWhere('id')->in($this->app->user->view->programs)->fi()
  66. ->orderBy($orderBy)
  67. ->fetchPairs();
  68. }
  69. /**
  70. * 获取项目集关联的产品列表。
  71. * Get the product associated with the program.
  72. *
  73. * @param int $programID
  74. * @param string $mode all|assign
  75. * @param string $status all|noclosed
  76. * @param string $append
  77. * @param string|int $shadow all | 0 | 1
  78. * @param bool $withProgram
  79. * @access public
  80. * @return array
  81. */
  82. public function getProductPairs($programID = 0, $mode = 'assign', $status = 'all', $append = '', $shadow = 0, $withProgram = false)
  83. {
  84. /* Get the top programID. */
  85. if($programID)
  86. {
  87. $program = $this->getByID($programID);
  88. $path = array_filter(explode(',', $program->path));
  89. $programID = current($path);
  90. }
  91. /* When mode equals assign and programID equals 0, you can query the standalone product. */
  92. if(!empty($append) && is_array($append)) $append = implode(',', $append);
  93. $views = empty($append) ? $this->app->user->view->products : $this->app->user->view->products . ",$append";
  94. $dao = $this->dao->select('t1.id, t1.name, t1.program')->from(TABLE_PRODUCT)->alias('t1')
  95. ->leftJoin(TABLE_PROGRAM)->alias('t2')->on('t1.program = t2.id')
  96. ->where('t1.deleted')->eq(0)
  97. ->andWhere("FIND_IN_SET('{$this->config->vision}', t1.vision)")
  98. ->beginIF($shadow !== 'all')->andWhere('t1.shadow')->eq((int)$shadow)->fi()
  99. ->beginIF($mode == 'assign')->andWhere('t1.program')->eq($programID)->fi()
  100. ->beginIF(strpos($status, 'noclosed') !== false)->andWhere('t1.status')->ne('closed')->fi()
  101. ->beginIF(!$this->app->user->admin)->andWhere('t1.id')->in($views)->fi()
  102. ->filterTpl('skip')
  103. ->orderBy('t2.order_asc,t1.line_desc,t1.order_asc');
  104. if(!$withProgram) return $dao->fetchPairs('id', 'name');
  105. /* Put products of current program first.*/
  106. $products = $dao->fetchGroup('program');
  107. if(!empty($products) && isset($products[$programID]) && $mode != 'assign' && $programID)
  108. {
  109. $currentProgramProducts = $products[$programID];
  110. unset($products[$programID]);
  111. array_unshift($products, $currentProgramProducts);
  112. }
  113. $productPairs = array();
  114. $productPrograms = $this->getPairs(true);
  115. foreach($products as $programProducts)
  116. {
  117. foreach($programProducts as $product)
  118. {
  119. $programName = zget($productPrograms, $product->program, '') . '/';
  120. $productPairs[$product->id] = $programName . $product->name;
  121. }
  122. }
  123. return $productPairs;
  124. }
  125. /**
  126. * 根据项目集ID获取项目集信息。
  127. * Get program by id.
  128. *
  129. * @param int $programID
  130. * @access public
  131. * @return object|false
  132. */
  133. public function getByID($programID = 0)
  134. {
  135. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getProgram();
  136. $program = $this->fetchByID($programID);
  137. if(!$program) return false;
  138. $program = $this->loadModel('file')->replaceImgURL($program, 'desc');
  139. return $program;
  140. }
  141. /**
  142. * 根据项目集ID列表获取项目集信息。
  143. * Get program pairs by id list.
  144. *
  145. * @param array $programIDList
  146. * @param string $orderBy
  147. * @access public
  148. * @return array
  149. */
  150. public function getPairsByList($programIDList = array(), $orderBy = 'order_asc')
  151. {
  152. return $this->dao->select('id, name')->from(TABLE_PROGRAM)
  153. ->where('id')->in($programIDList)
  154. ->andWhere('`type`')->eq('program')
  155. ->andWhere('deleted')->eq(0)
  156. ->orderBy($orderBy)
  157. ->fetchPairs();
  158. }
  159. /**
  160. * 获取项目集列表。
  161. * Get program list.
  162. *
  163. * @param string $status
  164. * @param string $orderBy
  165. * @param string $type top|child
  166. * @param array $topIdList
  167. * @param object $pager
  168. * @access public
  169. * @return array
  170. */
  171. public function getList($status = 'all', $orderBy = 'id_asc', $type = '', $topIdList = array(), $pager = null)
  172. {
  173. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getPrograms();
  174. $userViewIdList = trim($this->app->user->view->programs, ',') . ',' . trim($this->app->user->view->projects, ',');
  175. $userViewIdList = array_filter(explode(',', $userViewIdList));
  176. $objectIdList = array();
  177. if($this->app->rawMethod == 'browse')
  178. {
  179. $pathList = $this->dao->select('id,path')->from(TABLE_PROJECT)
  180. ->where('type')->in('program,project')
  181. ->beginIF(!$this->app->user->admin)->andWhere('id')->in($userViewIdList)->fi()
  182. ->andWhere('deleted')->eq(0)
  183. ->orderBy('id_asc')
  184. ->fetchPairs('id');
  185. foreach($pathList as $path)
  186. {
  187. if($type == 'child' && !empty($topIdList))
  188. {
  189. $topID = $this->getTopByPath($path);
  190. if(!in_array($topID, $topIdList)) continue;
  191. }
  192. foreach(explode(',', trim($path, ',')) as $pathID) $objectIdList[$pathID] = $pathID;
  193. }
  194. }
  195. return $this->dao->select('*')->from(TABLE_PROGRAM)
  196. ->where('type')->in('program,project')
  197. ->andWhere('deleted')->eq(0)
  198. ->andWhere('vision')->eq($this->config->vision)
  199. ->beginIF(strpos(',all,unclosed,delayed,', ",{$status},") === false)->andWhere('status')->in($status)->fi()
  200. ->beginIF($status == 'unclosed')->andWhere('status')->ne('closed')->fi()
  201. ->beginIF($status == 'delayed')->andWhere('end')->gt('1970-1-1')->andWhere('end')->lt(date(DT_DATE1))->andWhere('status')->notin('done,closed,suspended')->fi()
  202. ->beginIF($this->app->rawMethod == 'browse' && $type === 'top')->andWhere('parent')->eq(0)->fi()
  203. ->beginIF($this->app->rawMethod == 'browse' && ($type === 'child' or !$this->app->user->admin))->andWhere('id')->in($objectIdList)->fi()
  204. ->beginIF(!$this->app->user->admin && $this->app->rawMethod != 'browse')->andWhere('id')->in($userViewIdList)->fi()
  205. ->beginIF(defined('RUN_MODE') && RUN_MODE == 'api' && !$this->cookie->showClosed)->andWhere('status')->ne('closed')->fi()
  206. ->orderBy($orderBy)
  207. ->page($pager)
  208. ->fetchAll('id');
  209. }
  210. /**
  211. * 根据查询ID获取项目集列表。
  212. * Get program list by search.
  213. *
  214. * @param string $orderBy
  215. * @param int $queryID
  216. * @param bool $hasProject
  217. * @param object $pager
  218. * @access public
  219. * @return array
  220. */
  221. public function getListBySearch($orderBy = 'id_asc', $queryID = 0, $hasProject = false, $pager = null)
  222. {
  223. if($this->session->programQuery == false) $this->session->set('programQuery', ' 1 = 1');
  224. if($queryID)
  225. {
  226. $this->session->set('programQuery', ' 1 = 1');
  227. $query = $this->loadModel('search')->getQuery($queryID);
  228. if($query)
  229. {
  230. $this->session->set('programQuery', $query->sql);
  231. $this->session->set('programForm', $query->form);
  232. }
  233. }
  234. $query = $this->session->programQuery;
  235. $objectIdList = array();
  236. if(!$this->app->user->admin)
  237. {
  238. $objectIdList = trim($this->app->user->view->programs, ',') . ',' . trim($this->app->user->view->projects, ',');
  239. $objectIdList = array_filter(explode(',', $objectIdList));
  240. asort($objectIdList);
  241. if($this->app->rawMethod == 'browse')
  242. {
  243. $pathList = $this->dao->select('id,path')->from(TABLE_PROJECT)->where('id')->in($objectIdList)->andWhere('deleted')->eq(0)->fetchPairs('id', 'path');
  244. foreach($pathList as $path)
  245. {
  246. foreach(explode(',', trim($path, ',')) as $pathID) $objectIdList[$pathID] = $pathID;
  247. }
  248. }
  249. $objectIdList = array_unique($objectIdList);
  250. }
  251. return $this->dao->select('*')->from(TABLE_PROGRAM)->where('deleted')->eq(0)
  252. ->andWhere('vision')->eq($this->config->vision)
  253. ->beginIF(!$hasProject)->andWhere('type')->eq('program')->fi()
  254. ->beginIF($hasProject)->andWhere('type')->in('program,project')->fi()
  255. ->beginIF($query)->andWhere($query)->fi()
  256. ->beginIF(!$this->app->user->admin)->andWhere('id')->in($objectIdList)->fi()
  257. ->orderBy($orderBy)
  258. ->page($pager)
  259. ->fetchAll('id');
  260. }
  261. /**
  262. * 获取看板统计的相关数据。
  263. * Get Kanban statistics data.
  264. *
  265. * @param array $programs
  266. * @access public
  267. * @return array
  268. */
  269. public function getKanbanStatisticData($programs)
  270. {
  271. $productGroup = $this->getProductByProgram(array_keys($programs));
  272. $productIdList = array();
  273. foreach($productGroup as $programID => $products)
  274. {
  275. foreach($products as $product)
  276. {
  277. $productIdList[$product->id] = $product->id;
  278. if($product->shadow) $product->name = $product->name . ' (' . $this->lang->project->common . ')';
  279. }
  280. }
  281. /* Get all plans under products. */
  282. $planGroup = $this->loadModel('productplan')->getProductPlans($productIdList, helper::today());
  283. /* Get all products linked projects. */
  284. $projectGroup = $this->loadModel('project')->getGroupByProduct($productIdList, 'wait,doing');
  285. $projectIdList = array();
  286. foreach($projectGroup as $projects) $projectIdList = array_merge($projectIdList, array_keys($projects));
  287. /* Get all releases under products. */
  288. $releaseGroup = $this->loadModel('release')->getGroupByProduct($productIdList);
  289. /* Get doing executions. */
  290. $doingExecutions = $this->dao->select('id, project, name, end')->from(TABLE_EXECUTION)
  291. ->where('type')->in('sprint,stage,kanban')
  292. ->andWhere('status')->eq('doing')
  293. ->andWhere('deleted')->eq(0)
  294. ->andWhere('multiple')->ne(0)
  295. ->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->sprints)->fi()
  296. ->orderBy('id_asc')
  297. ->fetchAll('project');
  298. $executionPairs = array();
  299. foreach($doingExecutions as $execution) $executionPairs[$execution->id] = $execution->id;
  300. return array($productGroup, $planGroup, $releaseGroup, $projectGroup, $doingExecutions);
  301. }
  302. /**
  303. * 获取用户参与的项目集列表信息。
  304. * Get involved programs by user.
  305. *
  306. * @param string $account
  307. * @access public
  308. * @return array
  309. */
  310. public function getInvolvedPrograms($account)
  311. {
  312. $involvedProgramIdList = array();
  313. /* All objects in program table. */
  314. $objects = $this->dao->select('id,type,project,parent,path,openedBy,PM')->from(TABLE_PROGRAM)->where('deleted')->eq(0)->fetchAll('id');
  315. foreach($objects as $id => $object)
  316. {
  317. if($object->openedBy != $account && $object->PM != $account) continue;
  318. $programID = $this->getProgramIDByObject($id, $object, $objects);
  319. if($programID) $involvedProgramIdList[$programID] = $programID;
  320. }
  321. /* All involves in stakeholder table. */
  322. $stakeholders = $this->dao->select('t1.objectID, t2.type')->from(TABLE_STAKEHOLDER)->alias('t1')
  323. ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.objectID = t2.id')
  324. ->where('t1.objectType')->in("program,project")
  325. ->andWhere('t1.user')->eq($account)
  326. ->fetchAll('objectID');
  327. foreach($stakeholders as $objectID => $object)
  328. {
  329. $programID = $this->getProgramIDByObject($objectID, $object, $objects);
  330. if($programID) $involvedProgramIdList[$programID] = $programID;
  331. }
  332. /* All involves in team table. */
  333. $teams = $this->dao->select('t1.root, t2.project, t2.type')->from(TABLE_TEAM)->alias('t1')
  334. ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.root = t2.id')
  335. ->where('t1.account')->eq($account)
  336. ->andWhere('t1.type')->in('project,execution')
  337. ->fetchAll('root');
  338. foreach($teams as $objectID => $object)
  339. {
  340. $programID = $this->getProgramIDByObject($objectID, $object, $objects);
  341. if($programID) $involvedProgramIdList[$programID] = $programID;
  342. }
  343. /* All involves in products table. */
  344. $products = $this->dao->select('id, program, createdBy, PO, QD, RD')->from(TABLE_PRODUCT)
  345. ->where('deleted')->eq(0)
  346. ->andWhere("(createdBy = '$account' or PO = '$account' or QD = '$account' or RD = '$account')")
  347. ->fetchAll('id');
  348. foreach($products as $id => $product) $involvedProgramIdList[$product->program] = $product->program;
  349. return $this->dao->select('id')->from(TABLE_PROGRAM)
  350. ->where('deleted')->eq(0)
  351. ->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->programs)->fi()
  352. ->andWhere('id')->in($involvedProgramIdList)
  353. ->andWhere('grade')->eq(1)
  354. ->fetchPairs();
  355. }
  356. /**
  357. * 获取项目/执行的所属项目集ID。
  358. * Get the programID of the project/execution.
  359. *
  360. * @param int $objectID
  361. * @param object $object
  362. * @param array $objects
  363. * @access public
  364. * @return int
  365. */
  366. public function getProgramIDByObject($objectID, $object, $objects)
  367. {
  368. if($object->type == 'program') return $objectID;
  369. if($object->type == 'project')
  370. {
  371. $project = zget($objects, $objectID, array());
  372. if(!$project || !$project->parent) return 0;
  373. return $this->getTopByPath($project->path);
  374. }
  375. if(in_array($object->type, array('sprint', 'stage', 'kanban')))
  376. {
  377. $execution = zget($objects, $objectID, array());
  378. if(!$execution) return 0;
  379. $project = zget($objects, $execution->project, array());
  380. if(!$project || !$project->parent) return 0;
  381. return $this->getTopByPath($project->path);
  382. }
  383. return 0;
  384. }
  385. /**
  386. * 将团队成员等统计信息,追加到对应的项目中。
  387. * Append statistics fields to projects.
  388. *
  389. * @param array $projects
  390. * @param array $teams array keys are hours, teams and leftTasks.
  391. * @access public
  392. * @return array
  393. */
  394. public function appendStatToProjects($projects, $teams = array())
  395. {
  396. if(empty($projects)) return array();
  397. /* Get the number of left tasks. */
  398. $leftTasks = array();
  399. $executions = $this->loadModel('project')->getExecutionList(array_keys($projects));
  400. if($this->cookie->projectType && $this->cookie->projectType == 'bycard')
  401. {
  402. $leftTasks = $this->dao->select('t2.parent as project, COUNT(1) AS tasks')->from(TABLE_TASK)->alias('t1')
  403. ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.execution = t2.id')
  404. ->where('t1.execution')->in(array_keys($executions))
  405. ->andWhere('t1.status')->notIn('cancel,closed')
  406. ->groupBy('t2.parent')
  407. ->fetchAll('project');
  408. }
  409. /* Process projects. */
  410. $stats = array();
  411. foreach($projects as $projectID => $project)
  412. {
  413. if(helper::isZeroDate($project->end)) $project->end = '';
  414. /* Judge whether the project is delayed. */
  415. if($project->status != 'done' && $project->status != 'closed' && $project->status != 'suspended')
  416. {
  417. $delay = empty($project->end) ? 0 : helper::diffDate(helper::today(), $project->end);
  418. if($delay > 0) $project->delay = $delay;
  419. }
  420. /* Merge project team. */
  421. $project->teamCount = 0;
  422. $project->teamMembers = array();
  423. $project->leftTasks = isset($leftTasks[$project->id]) ? $leftTasks[$project->id]->tasks : '—';
  424. if(!empty($teams))
  425. {
  426. $project->teamCount = isset($teams[$project->id]) ? count($teams[$project->id]) : 0;
  427. $project->teamMembers = isset($teams[$project->id]) ? array_keys($teams[$project->id]) : array();
  428. }
  429. $stats[$projectID] = $project;
  430. }
  431. return $stats;
  432. }
  433. /**
  434. * 获取项目列表数据。
  435. * Get project list data.
  436. *
  437. * @param int $programID
  438. * @param string $browseType all|wait|undone|doing|suspended|closed|bysearch|review
  439. * @param int $queryID
  440. * @param string $orderBy
  441. * @param string $programTitle 0|base|end
  442. * @param bool $queryAll
  443. * @param object|null $pager
  444. * @access public
  445. * @return object[]
  446. */
  447. public function getProjectList($programID = 0, $browseType = 'all', $queryID = 0, $orderBy = 'id_desc', $programTitle = '', $queryAll = false, $pager = null)
  448. {
  449. $path = '';
  450. if($programID) $path = $this->getByID($programID)->path;
  451. /* Get query project SQL. */
  452. $query = '';
  453. if($browseType == 'bysearch')
  454. {
  455. $this->loadModel('search')->setQuery('project', $queryID);
  456. $query = str_replace('`id`','t1.id', $this->session->projectQuery);
  457. }
  458. $stmt = $this->dao->select("DISTINCT t1.*, CAST(NULLIF(t1.budget, 0) AS DECIMAL) AS budget")->from(TABLE_PROJECT)->alias('t1');
  459. if($this->cookie->involved || $browseType == 'involved') $stmt = $this->loadModel('project')->leftJoinInvolvedTable($stmt);
  460. $stmt->where('t1.deleted')->eq('0')
  461. ->andWhere('t1.vision')->eq($this->config->vision)
  462. ->beginIF($browseType == 'bysearch' && $query)->andWhere($query)->fi()
  463. ->andWhere('t1.type')->eq('project')
  464. ->beginIF(!in_array($browseType, array('all', 'undone', 'bysearch', 'review', 'unclosed', 'delayed', 'involved'), true))->andWhere('t1.status')->eq($browseType)->fi()
  465. ->beginIF($browseType == 'undone' || $browseType == 'unclosed')->andWhere('t1.status')->in('wait,doing')->fi()
  466. ->beginIF($browseType == 'delayed')->andWhere('t1.status')->notIn('done,closed,suspend')->andWhere('t1.end')->lt(helper::today())->fi()
  467. ->beginIF($browseType == 'review')
  468. ->andWhere("FIND_IN_SET('{$this->app->user->account}', t1.reviewers)")
  469. ->andWhere('t1.reviewStatus')->eq('doing')
  470. ->fi()
  471. ->beginIF($path)->andWhere('t1.path')->like($path . '%')->fi()
  472. ->beginIF(!$queryAll && !$this->app->user->admin)->andWhere('t1.id')->in($this->app->user->view->projects)->fi();
  473. if($this->cookie->involved) $stmt = $this->project->appendInvolvedCondition($stmt);
  474. $projectList = $stmt->orderBy($orderBy)->page($pager, 't1.id')->fetchAll('id');
  475. /* Determine how to display the name of the program. */
  476. if($programTitle and in_array($this->config->systemMode, array('ALM', 'PLM'))) $projectList = $this->batchProcessProgramName($projectList, $programTitle);
  477. return $projectList;
  478. }
  479. /**
  480. * 批量处理项目所属的项目集名称
  481. * Batch the name of the program to which the project belongs.
  482. *
  483. * @param array $projectList
  484. * @param string $programTitle
  485. * @access public
  486. * @return object[]
  487. */
  488. public function batchProcessProgramName($projectList, $programTitle = '')
  489. {
  490. $programList = $this->getPairs();
  491. foreach($projectList as $id => $project)
  492. {
  493. $path = explode(',', $project->path);
  494. $path = array_filter($path);
  495. array_pop($path);
  496. $programID = $programTitle == 'base' ? current($path) : end($path);
  497. if(empty($path) || $programID == $id) continue;
  498. $programName = isset($programList[$programID]) ? $programList[$programID] : '';
  499. if($programName) $projectList[$id]->name = $programName . '/' . $projectList[$id]->name;
  500. }
  501. return $projectList;
  502. }
  503. /**
  504. * 通过项目集ID获取干系人列表信息。
  505. * Get stakeholders by program id.
  506. *
  507. * @param int $programID
  508. * @param string $orderBy
  509. * @param object $paper
  510. * @access public
  511. * @return array
  512. */
  513. public function getStakeholders($programID = 0, $orderBy = 'id_desc', $pager = null)
  514. {
  515. return $this->dao->select('t2.account,t2.realname,t2.role,t2.qq,t2.mobile,t2.phone,t2.weixin,t2.email,t1.id,t1.type,t1.from,t1.key')->from(TABLE_STAKEHOLDER)->alias('t1')
  516. ->leftJoin(TABLE_USER)->alias('t2')->on('t1.user=t2.account')
  517. ->where('t1.objectID')->eq($programID)
  518. ->andWhere('t1.objectType')->eq('program')
  519. ->orderBy($orderBy)
  520. ->page($pager)
  521. ->fetchAll();
  522. }
  523. /**
  524. * 创建项目集。
  525. * Create a program.
  526. *
  527. * @access private
  528. * @return int|bool
  529. * @param object $program
  530. */
  531. public function create($program)
  532. {
  533. /* Redefines the language entries for the fields in the project table. */
  534. $this->lang->error->unique = $this->lang->error->repeat;
  535. foreach(explode(',', $this->config->program->create->requiredFields) as $field)
  536. {
  537. if(isset($this->lang->program->$field)) $this->lang->project->$field = $this->lang->program->$field;
  538. }
  539. $this->dao->insert(TABLE_PROGRAM)->data($program)
  540. ->autoCheck()
  541. ->batchcheck($this->config->program->create->requiredFields, 'notempty')
  542. ->checkIF($program->begin != '', 'begin', 'date')
  543. ->checkIF($program->end != '', 'end', 'date')
  544. ->checkIF($program->end != '', 'end', 'gt', $program->begin)
  545. ->checkIF(!empty($program->name), 'name', 'unique', "`type`='program' and `parent` = " . $this->dao->sqlobj->quote($program->parent) . " and `deleted` = '0'")
  546. ->checkFlow()
  547. ->exec();
  548. if(dao::isError()) return false;
  549. $programID = $this->dao->lastInsertId();
  550. $this->dao->update(TABLE_PROGRAM)->set('`order`')->eq($programID * 5)->where('id')->eq($programID)->exec(); // Save order.
  551. $whitelist = explode(',', $program->whitelist);
  552. $this->loadModel('personnel')->updateWhitelist($whitelist, 'program', $programID);
  553. $this->loadModel('file')->updateObjectID($this->post->uid, $programID, 'program');
  554. $this->setTreePath($programID);
  555. if($program->acl != 'open') $this->loadModel('user')->updateUserView(array($programID), 'program');
  556. return $programID;
  557. }
  558. /**
  559. * 更新项目集。
  560. * Update the program.
  561. *
  562. * @param int $programID
  563. * @param object $program
  564. * @access public
  565. * @return array|false
  566. */
  567. public function update($programID, $program)
  568. {
  569. $oldProgram = $this->fetchByID($programID);
  570. /* Update line root to top program. */
  571. if($program->parent) $this->dao->update(TABLE_MODULE)->set('root')->eq($program->parent)->where('root')->eq($programID)->andwhere('type')->eq('line')->exec();
  572. if(dao::isError()) return false;
  573. /* Redefines the language entries for the fields in the project table. */
  574. $this->app->loadLang('project');
  575. $this->lang->error->unique = $this->lang->error->repeat;
  576. foreach(explode(',', $this->config->program->edit->requiredFields) as $field)
  577. {
  578. if(isset($this->lang->program->$field)) $this->lang->project->$field = $this->lang->program->$field;
  579. }
  580. $this->dao->update(TABLE_PROGRAM)->data($program, 'syncPRJUnit,exchangeRate')
  581. ->autoCheck('begin,end')
  582. ->batchCheck($this->config->program->edit->requiredFields, 'notempty')
  583. ->checkIF($program->begin != '', 'begin', 'date')
  584. ->checkIF($program->end != '', 'end', 'date')
  585. ->checkIF($program->end != '', 'end', 'gt', $program->begin)
  586. ->checkIF(!empty($program->name), 'name', 'unique', "id!=$programID and `type`='program' and `parent` = " . $this->dao->sqlobj->quote($program->parent) . " and `deleted` = '0'")
  587. ->checkFlow()
  588. ->where('id')->eq($programID)
  589. ->exec();
  590. if(!dao::isError())
  591. {
  592. $this->loadModel('user');
  593. $this->loadModel('file')->updateObjectID($this->post->uid, $programID, 'project');
  594. $this->loadModel('personnel')->updateWhitelist(explode(',', $program->whitelist), 'program', $programID);
  595. if($program->acl != 'open') $this->user->updateUserView(array($programID), 'program');
  596. /* If the program changes, the authorities of programs and projects under the program should be refreshed. */
  597. $children = $this->dao->select('id, type')->from(TABLE_PROGRAM)->where('path')->like("%,{$programID},%")->andWhere('id')->ne($programID)->andWhere('acl')->eq('program')->fetchGroup('type', 'id');
  598. foreach($children as $type => $idList) $this->user->updateUserView(array_keys($idList), $type);
  599. if(isset($program->PM) and $program->PM != $oldProgram->PM)
  600. {
  601. $productIdList = $this->dao->select('id')->from(TABLE_PRODUCT)->where('program')->eq($programID)->fetchPairs('id');
  602. $this->user->updateUserView($productIdList, 'product');
  603. }
  604. if($oldProgram->parent != $program->parent)
  605. {
  606. $this->processNode($programID, $program->parent, $oldProgram->path, $oldProgram->grade);
  607. $this->programTao->fixLinkedProduct($programID, $program->parent, $oldProgram->parent, $oldProgram->path);
  608. }
  609. return common::createChanges($oldProgram, $program);
  610. }
  611. return false;
  612. }
  613. /**
  614. * 关闭一个项目集。
  615. * Close a program.
  616. *
  617. * @param object $oldProgram
  618. * @param object $program
  619. * @access public
  620. * @return array|bool
  621. */
  622. public function close($program, $oldProgram)
  623. {
  624. $program = $this->loadModel('file')->processImgURL($program, $this->config->program->editor->close['id'], (string)$this->post->uid);
  625. $this->dao->update(TABLE_PROJECT)->data($program)
  626. ->autoCheck()
  627. ->checkIF($program->realEnd != '', 'realEnd', 'le', helper::today())
  628. ->checkIF($program->realEnd != '', 'realEnd', 'ge', $oldProgram->realBegan)
  629. ->checkFlow()
  630. ->where('id')->eq($oldProgram->id)
  631. ->exec();
  632. if(dao::isError()) return false;
  633. return common::createChanges($oldProgram, $program);
  634. }
  635. /**
  636. * 激活一个项目集。
  637. * Activate a program.
  638. *
  639. * @param object $program
  640. * @param object $oldProgram
  641. * @access public
  642. * @return array|false
  643. */
  644. public function activate($program, $oldProgram)
  645. {
  646. if($program->begin > $program->end)
  647. {
  648. dao::$errors['end'] = sprintf($this->lang->error->ge, $this->lang->program->end, $this->lang->program->begin);
  649. return false;
  650. }
  651. if(!helper::isZeroDate($oldProgram->realBegan)) $program->realBegan = helper::today();
  652. $program = $this->loadModel('file')->processImgURL($program, $this->config->program->editor->activate['id'], (string)$this->post->uid);
  653. $this->dao->update(TABLE_PROJECT)->data($program)
  654. ->autoCheck()
  655. ->checkFlow()
  656. ->where('id')->eq($oldProgram->id)
  657. ->exec();
  658. if(dao::isError()) return false;
  659. return common::createChanges($oldProgram, $program);
  660. }
  661. /**
  662. * 挂起一个项目集。
  663. * Suspend a program.
  664. *
  665. * @param int $programID
  666. * @param object $postData
  667. * @access public
  668. * @return array
  669. */
  670. public function suspend($programID, $postData)
  671. {
  672. $oldProgram = $this->getByID($programID);
  673. $program = $this->loadModel('file')->processImgURL($postData, $this->config->program->editor->suspend['id'], $postData->uid);
  674. $this->dao->update(TABLE_PROJECT)->data($program, 'comment,uid')
  675. ->autoCheck()
  676. ->checkFlow()
  677. ->where('id')->eq($programID)
  678. ->exec();
  679. $newProgram = $this->getByID($programID);
  680. if(dao::isError()) return false;
  681. $changes = common::createChanges($oldProgram, $newProgram);
  682. if($postData->comment != '' or !empty($changes))
  683. {
  684. $actionID = $this->loadModel('action')->create('program', $programID, 'Suspended', $postData->comment);
  685. $this->action->logHistory($actionID, $changes);
  686. }
  687. return true;
  688. }
  689. /**
  690. * 获取一级项目集id:name的键值对。
  691. * Get top program pairs.
  692. *
  693. * @param string $model
  694. * @param string $mode
  695. * @param bool $isQueryAll
  696. * @param array $appendPrograms
  697. * @access public
  698. * @return array
  699. */
  700. public function getTopPairs($mode = '', $isQueryAll = false, $appendPrograms = array())
  701. {
  702. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getProgramPairs();
  703. $topPairs = $this->dao->select('id,name')->from(TABLE_PROGRAM)
  704. ->where('type')->eq('program')
  705. ->andWhere('grade')->eq(1)
  706. ->beginIF(strpos($mode, 'noclosed') !== false)->andWhere('status')->ne('closed')->fi()
  707. ->beginIF(strpos($mode, 'withDeleted') === false)->andWhere('deleted')->eq(0)->fi()
  708. ->beginIF(!$isQueryAll && !$this->app->user->admin)->andWhere('id')->in($this->app->user->view->programs)->fi()
  709. ->beginIF(!empty($appendPrograms))->orWhere('id')->in($appendPrograms)->fi()
  710. ->orderBy('`order` asc')
  711. ->fetchPairs();
  712. if(strpos($mode, 'withDeleted') !== false)
  713. {
  714. $deletedTopPairs = $this->dao->select('id, name')->from(TABLE_PROGRAM)
  715. ->where('type')->eq('program')
  716. ->andWhere('grade')->eq(1)
  717. ->andWhere('deleted')->eq(1)
  718. ->fetchPairs();
  719. foreach($topPairs as $id => $name)
  720. {
  721. if(isset($deletedTopPairs[$id])) $topPairs[$id] .= ' (' . $this->lang->program->deleted . ')';
  722. }
  723. }
  724. return $topPairs;
  725. }
  726. /**
  727. * 获取顶级项目集的ID。
  728. * Get top program by id.
  729. *
  730. * @param int $programID
  731. * @access public
  732. * @return int
  733. */
  734. public function getTopByID($programID)
  735. {
  736. if(empty($programID)) return 0;
  737. $program = $this->getByID($programID);
  738. if(empty($program)) return 0;
  739. return $this->getTopByPath($program->path);
  740. }
  741. /**
  742. * 通过路径获取顶级项目集的ID。
  743. * Get top program by path.
  744. *
  745. * @param string $path
  746. * @access public
  747. * @return int
  748. */
  749. public function getTopByPath($path)
  750. {
  751. $paths = explode(',', trim($path, ','));
  752. return (int)$paths[0];
  753. }
  754. /**
  755. * 该项目集下是否有未关闭的子项目集或项目。
  756. * Judge whether there is an unclosed programs or projects.
  757. *
  758. * @param object $program
  759. * @access public
  760. * @return bool
  761. */
  762. public function hasUnfinishedChildren($program)
  763. {
  764. $count = $this->dao->select("id")->from(TABLE_PROJECT)
  765. ->where('type')->in('program, project')
  766. ->andWhere('path')->like($program->path . '%')
  767. ->andWhere('id')->ne($program->id)
  768. ->andWhere('status')->ne('closed')
  769. ->andWhere('deleted')->eq('0')
  770. ->count();
  771. return $count != 0;
  772. }
  773. /**
  774. * 更新项目集及子项的用户视图。
  775. * Update user view of program and its children.
  776. *
  777. * @param int $programID
  778. * @param array $account
  779. * @access public
  780. * @return bool
  781. * @param mixed[] $accounts
  782. */
  783. public function updateChildUserView($programID = 0, $accounts = array())
  784. {
  785. $this->loadModel('user')->updateUserView(array($programID), 'program', $accounts);
  786. $programList = $this->dao->select('id')->from(TABLE_PROJECT)->where('path')->like("%,$programID,%")->andWhere('type')->eq('program')->fetchPairs();
  787. $projectList = $this->loadModel('project')->getPairsByProgram($programID, 'all', true);
  788. $productList = $this->loadModel('product')->getPairs('', $programID);
  789. if(!empty($programList)) $this->user->updateUserView($programList, 'program', $accounts);
  790. if(!empty($projectList)) $this->user->updateUserView(array_keys($projectList), 'project', $accounts);
  791. if(!empty($productList)) $this->user->updateUserView(array_keys($productList), 'product', $accounts);
  792. return !dao::isError();
  793. }
  794. /**
  795. * 判断操作按钮是否可以点击。
  796. * Judge an action is clickable or not.
  797. *
  798. * @param object $program
  799. * @param string $action
  800. * @access public
  801. * @return bool
  802. */
  803. public static function isClickable($program, $action)
  804. {
  805. $action = strtolower($action);
  806. if(empty($program)) return true;
  807. if(!isset($program->type)) return true;
  808. if($action == 'close') return $program->status != 'closed';
  809. if($action == 'activate') return $program->status == 'done' || $program->status == 'closed';
  810. if($action == 'suspend') return $program->status == 'wait' || $program->status == 'doing';
  811. if($action == 'start') return $program->status == 'wait' || $program->status == 'suspended';
  812. return true;
  813. }
  814. /**
  815. * 设置项目集的路径。
  816. * Set program tree path.
  817. *
  818. * @param int $programID
  819. * @access public
  820. * @return bool
  821. */
  822. public function setTreePath($programID)
  823. {
  824. $program = $this->dao->select('*')->from(TABLE_PROGRAM)->where('id')->eq($programID)->fetch();
  825. if(!$program) return false;
  826. $path['path'] = ",{$program->id},";
  827. $path['grade'] = 1;
  828. if($program->parent)
  829. {
  830. $parent = $this->dao->select('*')->from(TABLE_PROGRAM)->where('id')->eq($program->parent)->fetch();
  831. $path['path'] = $parent->path . "{$program->id},";
  832. $path['grade'] = $parent->grade + 1;
  833. }
  834. $this->dao->update(TABLE_PROGRAM)->set('path')->eq($path['path'])->set('grade')->eq($path['grade'])->where('id')->eq($program->id)->exec();
  835. return !dao::isError();
  836. }
  837. /**
  838. * 获取项目集的预算剩余。
  839. * Get budget left of program.
  840. *
  841. * @param object $parentProgram
  842. * @param int|float $leftBudget
  843. * @access public
  844. * @return float
  845. */
  846. public function getBudgetLeft($parentProgram, $leftBudget = 0)
  847. {
  848. if(empty($parentProgram->id)) return 0;
  849. $childGrade = $parentProgram->grade + 1;
  850. $childSumBudget = $this->dao->select("sum(budget) as sumBudget")->from(TABLE_PROGRAM)
  851. ->where('path')->like("%,{$parentProgram->id},%")
  852. ->andWhere('grade')->eq($childGrade)
  853. ->andWhere('deleted')->eq('0')
  854. ->fetch('sumBudget');
  855. $leftBudget += (float)$parentProgram->budget - (float)$childSumBudget;
  856. if($parentProgram->budget == 0 && $parentProgram->parent)
  857. {
  858. $parentParent = $this->getById($parentProgram->parent);
  859. return $this->getBudgetLeft($parentParent, $leftBudget);
  860. }
  861. return $leftBudget;
  862. }
  863. /**
  864. * 获取父项目集列表。
  865. * Get program parent pairs
  866. *
  867. * @param string $model
  868. * @param string $mode noclosed|all
  869. * @param bool $showRoot
  870. * @access public
  871. * @return array
  872. */
  873. public function getParentPairs($model = '', $mode = 'noclosed', $showRoot = true)
  874. {
  875. $programList = $this->dao->select('id,name,parent,path,grade')->from(TABLE_PROGRAM)
  876. ->where('type')->eq('program')
  877. ->andWhere('deleted')->eq(0)
  878. ->beginIF($model)->andWhere('model')->eq($model)->fi()
  879. ->beginIF(strpos($mode, 'noclosed') !== false)->andWhere('status')->ne('closed')->fi()
  880. ->orderBy('grade desc, `order`')
  881. ->fetchAll('id');
  882. $treeMenu = array();
  883. foreach($programList as $program)
  884. {
  885. $programName = $showRoot ? '/' : '';
  886. $parentList = explode(',', trim($program->path, ','));
  887. foreach($parentList as $parentID)
  888. {
  889. if(empty($parentID) || empty($programList[$parentID])) continue;
  890. $programName .= $programList[$parentID]->name . '/';
  891. }
  892. $programName = str_replace('|', '&#166;', rtrim($programName, '/'));
  893. $programName .= "|$program->id\n";
  894. if(!isset($treeMenu[$program->parent])) $treeMenu[$program->parent] = '';
  895. $treeMenu[$program->parent] .= $programName;
  896. if(isset($treeMenu[$program->id]) && !empty($treeMenu[$program->id])) $treeMenu[$program->parent] .= $treeMenu[$program->id];
  897. }
  898. ksort($treeMenu);
  899. $topMenu = array_shift($treeMenu);
  900. $topMenu = empty($topMenu) ? '' : trim($topMenu);
  901. $topMenu = explode("\n", $topMenu);
  902. $lastMenu = $showRoot ? array('/') : array();
  903. foreach($topMenu as $menu)
  904. {
  905. if(strpos($menu, '|') === false) continue;
  906. list($label, $moduleID) = explode('|', $menu);
  907. /* Ignore the no priv programs. */
  908. if(!$this->app->user->admin && strpos($mode, 'all') === false && strpos(",{$this->app->user->view->programs},", ",{$moduleID},") === false) continue;
  909. $lastMenu[$moduleID] = str_replace('&#166;', '|', $label);
  910. }
  911. return $lastMenu;
  912. }
  913. /**
  914. * 根据项目集ID列表获取父项目集的负责人。
  915. * Get parent PM by programIdList.
  916. *
  917. * @param array $programIdList
  918. * @access public
  919. * @return array
  920. */
  921. public function getParentPM($programIdList)
  922. {
  923. $objects = $this->dao->select('id, path, parent')->from(TABLE_PROGRAM)->where('id')->in($programIdList)->andWhere('acl')->ne('open')->fetchAll('id');
  924. $parents = array();
  925. foreach($objects as $object)
  926. {
  927. if($object->parent == 0) continue;
  928. foreach(explode(',', $object->path) as $objectID)
  929. {
  930. if(empty($objectID) || $objectID == $object->id) continue;
  931. $parents[$objectID][] = $object->id;
  932. }
  933. }
  934. /* Get all parent PM.*/
  935. $parentPM = $this->dao->select('id, PM')->from(TABLE_PROGRAM)->where('id')->in(array_keys($parents))->andWhere('deleted')->eq('0')->fetchAll();
  936. $parentPMGroup = array();
  937. foreach($parentPM as $PM)
  938. {
  939. $subPrograms = zget($parents, $PM->id, array());
  940. foreach($subPrograms as $subProgramID) $parentPMGroup[$subProgramID][$PM->PM] = $PM->PM;
  941. }
  942. return $parentPMGroup;
  943. }
  944. /**
  945. * 修改子项目集和项目的层级。
  946. * Modify the subProgram and project grade.
  947. *
  948. * @param int $programID
  949. * @param int $parentID
  950. * @param string $oldPath
  951. * @param int $oldGrade
  952. * @access public
  953. * @return bool
  954. */
  955. public function processNode($programID, $parentID, $oldPath, $oldGrade)
  956. {
  957. $parent = $this->dao->select('id,parent,path,grade')->from(TABLE_PROGRAM)->where('id')->eq($parentID)->fetch();
  958. $childNodes = $this->dao->select('id,parent,path,grade,type')->from(TABLE_PROGRAM)
  959. ->where('path')->like("{$oldPath}%")
  960. ->andWhere('deleted')->eq(0)
  961. ->orderBy('grade')
  962. ->fetchAll();
  963. /* Process child node path and grade field. */
  964. foreach($childNodes as $childNode)
  965. {
  966. $hasProgram = strpos($childNode->path, ",{$programID},");
  967. if($hasProgram === false) continue;
  968. $path = substr($childNode->path, $hasProgram);
  969. /* Only program and project sets update grade. */
  970. $grade = in_array($childNode->type, array('program', 'project')) ? $childNode->grade - $oldGrade + 1 : $childNode->grade;
  971. if($parent)
  972. {
  973. $path = rtrim($parent->path, ',') . $path;
  974. $grade = in_array($childNode->type, array('program', 'project')) ? $parent->grade + $grade : $grade;
  975. }
  976. $this->dao->update(TABLE_PROGRAM)->set('path')->eq($path)->set('grade')->eq($grade)->where('id')->eq($childNode->id)->exec();
  977. }
  978. return !dao::isError();
  979. }
  980. /**
  981. * 获取项目统计数据。
  982. * Get project stats data.
  983. *
  984. * @param int $programID
  985. * @param string $browseType
  986. * @param int $queryID
  987. * @param string $orderBy
  988. * @param object|null $pager
  989. * @param string $programTitle
  990. * @param bool $queryAll
  991. * @access public
  992. * @return array
  993. */
  994. public function getProjectStats($programID = 0, $browseType = 'undone', $queryID = 0, $orderBy = 'id_desc', $programTitle = '', $queryAll = false, $pager = null)
  995. {
  996. if(commonModel::isTutorialMode()) return $this->loadModel('tutorial')->getProjectStats($browseType);
  997. $projects = $this->getProjectList($programID, $browseType, $queryID, $orderBy, $programTitle, $queryAll, $pager);
  998. if(empty($projects)) return array();
  999. /* Get the members of project teams. */
  1000. $teamMembers = $this->loadModel('project')->getTeamMemberGroup(array_keys($projects));
  1001. return $this->appendStatToProjects($projects, $teamMembers);
  1002. }
  1003. /**
  1004. * 根据项目集ID获取团队成员。
  1005. * Get program team member pairs.
  1006. *
  1007. * @param int $programID
  1008. * @access public
  1009. * @return array
  1010. */
  1011. public function getTeamMemberPairs($programID = 0)
  1012. {
  1013. $projectList = $this->getProjectList($programID);
  1014. if(!$projectList) return array();
  1015. $users = $this->dao->select("t2.id, t2.account, t2.realname")->from(TABLE_TEAM)->alias('t1')
  1016. ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account = t2.account')
  1017. ->where('t1.root')->in(array_keys($projectList))
  1018. ->andWhere('t1.type')->eq('project')
  1019. ->andWhere('t2.deleted')->eq(0)
  1020. ->fetchAll('account');
  1021. if(!$users) return array();
  1022. $firstLetter = '';
  1023. foreach($users as $account => $user)
  1024. {
  1025. if(empty($this->config->isINT)) $firstLetter = ucfirst(substr($user->account, 0, 1)) . ':';
  1026. $users[$account] = $firstLetter;
  1027. $users[$account] .= $user->realname ? $user->realname : $user->account;
  1028. }
  1029. return $users;
  1030. }
  1031. /**
  1032. * 创建默认的项目集。
  1033. * Create default program.
  1034. *
  1035. * @access public
  1036. * @return int
  1037. */
  1038. public function createDefaultProgram()
  1039. {
  1040. $defaultProgram = $this->loadModel('setting')->getItem('owner=system&module=common&section=global&key=defaultProgram');
  1041. if($defaultProgram)
  1042. {
  1043. $program = $this->dao->select('id')->from(TABLE_PROGRAM)->where('id')->eq($defaultProgram)->andWhere('deleted')->eq(0)->fetch();
  1044. if($program) return $defaultProgram;
  1045. }
  1046. $program = $this->dao->select('id')->from(TABLE_PROGRAM)->where('name')->eq($this->lang->program->defaultProgram)->andWhere('deleted')->eq(0)->fetch();
  1047. if($program) return $program->id;
  1048. $account = isset($this->app->user->account) ? $this->app->user->account : '';
  1049. $minBegin = $this->dao->select('min(`begin`) as min')->from(TABLE_PROJECT)->where('deleted')->eq(0)->fetch('min');
  1050. $program = new stdclass();
  1051. $program->name = $this->lang->program->defaultProgram;
  1052. $program->type = 'program';
  1053. $program->budgetUnit = 'CNY';
  1054. $program->status = 'doing';
  1055. $program->auth = 'extend';
  1056. $program->begin = !empty($minBegin) ? $minBegin : helper::today();
  1057. $program->end = LONG_TIME;
  1058. $program->openedBy = $account;
  1059. $program->openedDate = helper::now();
  1060. $program->openedVersion = $this->config->version;
  1061. $program->acl = 'open';
  1062. $program->grade = 1;
  1063. $program->vision = 'rnd';
  1064. $this->app->loadLang('program');
  1065. $this->app->loadLang('project');
  1066. $this->lang->project->name = $this->lang->program->name;
  1067. $this->dao->insert(TABLE_PROGRAM)->data($program)->exec();
  1068. if(dao::isError()) return false;
  1069. $programID = $this->dao->lastInsertId();
  1070. $this->dao->update(TABLE_PROGRAM)->set('path')->eq(",{$programID},")->set('`order`')->eq($programID * 5)->where('id')->eq($programID)->exec();
  1071. $this->loadModel('action')->create('program', $programID, 'openedbysystem');
  1072. return $programID;
  1073. }
  1074. /**
  1075. * 构造项目集列表的操作列数据。
  1076. * Build actions data.
  1077. *
  1078. * @param object $program
  1079. * @access public
  1080. * @return array
  1081. */
  1082. public function buildActions($program)
  1083. {
  1084. $actionsMap = $this->programTao->buildProgramActionsMap($program);
  1085. if($program->type == 'project') $actionsMap = array_merge($actionsMap, $this->programTao->buildProjectActionsMap($program));
  1086. return $actionsMap;
  1087. }
  1088. /**
  1089. * 获取项目集下的产品列表信息。
  1090. * Get product list information under the program.
  1091. *
  1092. * @param array $programIdList
  1093. * @access public
  1094. * @return array
  1095. */
  1096. public function getProductByProgram($programIdList = array())
  1097. {
  1098. return $this->dao->select('*')->from(TABLE_PRODUCT)
  1099. ->where('deleted')->eq(0)
  1100. ->andWhere('status')->ne('closed')
  1101. ->beginIF(!empty($programIdList))->andWhere('program')->in($programIdList)->fi()
  1102. ->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->products)->fi()
  1103. ->andWhere("FIND_IN_SET('{$this->config->vision}', vision)")
  1104. ->orderBy('order_asc')
  1105. ->fetchGroup('program');
  1106. }
  1107. /**
  1108. * 更新项目集排序。
  1109. * Update the order of the program.
  1110. *
  1111. * @param int $programID
  1112. * @param int $order
  1113. * @access public
  1114. * @return bool
  1115. */
  1116. public function updateOrder($programID, $order)
  1117. {
  1118. $this->dao->update(TABLE_PROGRAM)->set('`order`')->eq($order)->where('id')->eq($programID)->exec();
  1119. return !dao::isError();
  1120. }
  1121. /**
  1122. * 批量移除项目集下的关系人。
  1123. * Batch unlink program stakeholders.
  1124. *
  1125. * @param int $programID
  1126. * @param array $stakeholderIdList
  1127. * @access public
  1128. * @return bool
  1129. */
  1130. public function batchUnlinkStakeholders($programID, $stakeholderIdList)
  1131. {
  1132. $accountList = $this->dao->select('user')->from(TABLE_STAKEHOLDER)->where('id')->in($stakeholderIdList)->fetchPairs('user');
  1133. $this->dao->delete()->from(TABLE_STAKEHOLDER)->where('id')->in($stakeholderIdList)->exec();
  1134. if(dao::isError()) return false;
  1135. $this->updateChildUserView($programID, $accountList);
  1136. return !dao::isError();
  1137. }
  1138. /**
  1139. * 通过项目集ID列表批量获取项目集基本数据。
  1140. * Get program base data with program ID array.
  1141. *
  1142. * @param array $programIdList
  1143. * @access public
  1144. * @return array
  1145. */
  1146. public function getBaseDataList($programIdList)
  1147. {
  1148. return $this->dao->select('id,name,PM,path,parent,type')
  1149. ->from(TABLE_PROGRAM)
  1150. ->where('id')->in($programIdList)
  1151. ->andWhere('deleted')->eq('0')
  1152. ->andWhere('type')->eq('program')
  1153. ->fetchAll('id');
  1154. }
  1155. /**
  1156. * 刷新项目集的统计数据。
  1157. * Refresh stats fields(estimate,consumed,left,progress) of program, project, execution.
  1158. *
  1159. * @param bool $refreshAll
  1160. * @access public
  1161. * @return bool
  1162. */
  1163. public function refreshStats($refreshAll = false)
  1164. {
  1165. $updateTime = zget($this->app->config->global, 'projectStatsTime', '');
  1166. if($updateTime && time() - strtotime($updateTime) < $this->config->program->refreshInterval && !$refreshAll) return true;
  1167. /*
  1168. * If projectStatsTime is before two weeks ago, refresh all executions directly.
  1169. * Else only refresh the latest executions in action table.
  1170. */
  1171. $projects = array();
  1172. if($updateTime < date('Y-m-d', strtotime('-14 days')) or $refreshAll)
  1173. {
  1174. $projects = $this->dao->select('id,project,model,deleted')->from(TABLE_PROJECT)->fetchAll('id');
  1175. }
  1176. else
  1177. {
  1178. $projects = $this->dao->select('distinct t1.project,t2.model,t2.deleted')->from(TABLE_ACTION)->alias('t1')
  1179. ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
  1180. ->where('t1.`date`')->ge($updateTime)
  1181. ->andWhere('t1.project')->ne(0)
  1182. ->fetchAll('project');
  1183. }
  1184. if(empty($projects)) return true;
  1185. $now = helper::now();
  1186. /* 1. Refresh stats to db. */
  1187. $this->programTao->updateStats(array_keys($projects));
  1188. /* 2. Update programStatsTime. */
  1189. $this->programTao->updateProgress();
  1190. /* 3. Update projectStatsTime in config. */
  1191. $this->loadModel('setting')->setItem('system.common.global.projectStatsTime', $now);
  1192. $this->app->config->global->projectStatsTime = $now;
  1193. /* 4. Clear actions older than 30 days. */
  1194. $this->loadModel('action')->cleanActions();
  1195. return !dao::isError();
  1196. }
  1197. /**
  1198. * 刷新项目的统计数据。
  1199. * Refresh stats fields(estimate,consumed,left,progress) of project.
  1200. *
  1201. * @param bool $refreshAll
  1202. * @access public
  1203. * @return void
  1204. * @param int $projectID
  1205. */
  1206. public function refreshProjectStats($projectID)
  1207. {
  1208. $this->programTao->updateStats(array($projectID));
  1209. }
  1210. /**
  1211. * Check the privilege.
  1212. *
  1213. * @param int $programID
  1214. * @access public
  1215. * @return bool
  1216. */
  1217. public function checkPriv($programID)
  1218. {
  1219. return !empty($programID) && ($this->app->user->admin || (strpos(",{$this->app->user->view->programs},", ",{$programID},") !== false));
  1220. }
  1221. /**
  1222. * 通过项目集ID获取子项目集和项目。
  1223. *
  1224. * @param int $programID
  1225. * @access public
  1226. * @return array
  1227. */
  1228. public function getChildrenPairsByID($programID)
  1229. {
  1230. return $this->dao->select('id, name')->from(TABLE_PROGRAM)->where('parent')->eq($programID)->andWhere('deleted')->eq('0')->fetchPairs();
  1231. }
  1232. /**
  1233. * 通过项目集ID获取产品。
  1234. *
  1235. * @param int $programID
  1236. * @access public
  1237. * @return array
  1238. */
  1239. public function getProductPairsByID($programID)
  1240. {
  1241. return $this->dao->select('id, name')->from(TABLE_PRODUCT)->where('program')->eq($programID)->andWhere('deleted')->eq('0')->fetchPairs();
  1242. }
  1243. /*
  1244. * Set program menu.
  1245. *
  1246. * @param int $programID
  1247. * @access public
  1248. * @return void
  1249. */
  1250. /**
  1251. * @param int $programID
  1252. */
  1253. public function setMenu($programID)
  1254. {
  1255. $this->lang->switcherMenu = $this->getSwitcher($programID);
  1256. common::setMenuVars('program', $programID);
  1257. }
  1258. /*
  1259. * Get program swapper.
  1260. *
  1261. * @param int $programID
  1262. * @access private
  1263. * @return string
  1264. */
  1265. /**
  1266. * @param int $programID
  1267. */
  1268. public function getSwitcher($programID = 0)
  1269. {
  1270. $currentProgramName = '';
  1271. $currentModule = $this->app->moduleName;
  1272. $currentMethod = $this->app->methodName;
  1273. if($programID)
  1274. {
  1275. helper::setCookie("lastProgram", $programID, $this->config->cookieLife, $this->config->webRoot, '', false, true);
  1276. $currentProgram = $this->getById($programID);
  1277. $currentProgramName = $currentProgram->name;
  1278. }
  1279. else
  1280. {
  1281. $currentProgramName = $this->lang->program->all;
  1282. }
  1283. $dropMenuLink = helper::createLink('program', 'ajaxGetDropMenu', "objectID=$programID&module=$currentModule&method=$currentMethod");
  1284. $output = "<div class='btn-group header-btn' id='swapper'><button data-toggle='dropdown' type='button' class='btn' id='currentItem' title='{$currentProgramName}'><span class='text'>{$currentProgramName}</span> <span class='caret' style='margin-bottom: -1px'></span></button><div id='dropMenu' class='dropdown-menu search-list' data-ride='dropmenu' data-url='$dropMenuLink'>";
  1285. $output .= '<div class="input-control search-box has-icon-left has-icon-right search-example"><input type="search" class="form-control search-input" /><label class="input-control-icon-left search-icon"><i class="icon icon-search"></i></label><a class="input-control-icon-right search-clear-btn"><i class="icon icon-close icon-sm"></i></a></div>';
  1286. $output .= "</div></div>";
  1287. return $output;
  1288. }
  1289. }