| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004 |
- <?php
- class projectModel extends model
- {
- /**
- * 根据对象类型获取访问控制列表。
- * Get access control list by object type.
- *
- * @param string $objectType
- * @param string $account
- * @access public
- * @return array
- */
- public function getAclListByObjectType($objectType, $account = '')
- {
- return $this->dao->select('id, account, objectType, objectID')->from(TABLE_ACL)->where('objectType')->in($objectType)
- ->beginIF($account)->andWhere('account')->eq($account)->fi()
- ->fetchAll('id');
- }
- /**
- * 根据权限控制范围获取项目。
- * Get projects by acl.
- *
- * @param string $acl
- * @param array $idList
- * @access public
- * @return array
- */
- public function getListByAcl($acl, $idList = [])
- {
- $projects = $this->getListByAclAndType($acl);
- if(!$projects) return [];
- if(!$idList) return $projects;
- return array_intersect_key($projects, array_flip($idList)) ?: [];
- }
- /**
- * 根据权限控制范围和类型获取项目。
- * Get projects by acl and type.
- *
- * @param string $acl
- * @param string $type
- * @access public
- * @return array
- */
- public function getListByAclAndType($acl, $type = '')
- {
- return $this->dao->select('id, project, type, parent, path, openedBy, PO, PM, QD, RD, acl')->from(TABLE_PROJECT)
- ->where('acl')->in($acl)
- ->beginIF($type)->andWhere('type')->in($type)->fi()
- ->filterTpl('skip')
- ->fetchAll('id');
- }
- /**
- * 根据类型获取团队。
- * Get teams by type.
- *
- * @param string $type
- * @param string $account
- * @access public
- * @return array
- */
- public function getTeamListByType($type, $account = '')
- {
- return $this->dao->select('id, root, type, account')->from(TABLE_TEAM)->where('type')->in($type)
- ->beginIF($account)->andWhere('account')->eq($account)->fi()
- ->fetchAll('id');
- }
- /**
- * 获取当前登录用户有权限查看的项目列表.
- * Get project list by current user.
- *
- * @param string $fields
- * @param string $filterTpl
- * @access public
- * @return array
- */
- public function getListByCurrentUser($fields = '*', $filterTpl = '')
- {
- if($filterTpl == 'skip') dao::$filterTpl = 'skip';
- return $this->dao->select($fields)->from(TABLE_PROJECT)
- ->where('type')->eq('project')
- ->beginIF($this->config->vision)->andWhere('vision')->eq($this->config->vision)->fi()
- ->andWhere('deleted')->eq(0)
- ->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->projects)->fi()
- ->beginIF($filterTpl == 'onlyTpl')->andWhere('isTpl')->eq('1')->fi()
- ->orderBy('order_asc,id_desc')
- ->fetchAll('id');
- }
- /**
- * 获取当前登录用户参与的项目列表.
- * Get project list by current user.
- *
- * @param string $fields
- * @access public
- * @return array
- */
- public function getInvolvedListByCurrentUser($fields = 't1.*')
- {
- $stmt = $this->dao->select($fields)->from(TABLE_PROJECT)->alias('t1');
- $stmt = $this->leftJoinInvolvedTable($stmt);
- $stmt->where('t1.type')->eq('project')
- ->beginIF($this->config->vision)->andWhere('t1.vision')->eq($this->config->vision)->fi()
- ->andWhere('t1.deleted')->eq(0)
- ->beginIF(!$this->app->user->admin)->andWhere('t1.id')->in($this->app->user->view->projects)->fi();
- $stmt = $this->appendInvolvedCondition($stmt);
- return $stmt->orderBy('order_asc,id_desc')
- ->fetchAll('id');
- }
- /**
- * 左链接 "我参与的" 条件需要的表,传入的$stmt t1应为 TABLE_PROJECT 表
- * LeftJoin "I participate" condition required table.
- *
- * @param object $stmt
- * @access public
- * @return object
- */
- public function leftJoinInvolvedTable($stmt)
- {
- return $stmt->leftJoin(TABLE_TEAM)->alias('t2')->on('t1.id = t2.root')
- ->leftJoin(TABLE_STAKEHOLDER)->alias('t3')->on('t1.id=t3.objectID');
- }
- /**
- * 添加 "我参与的" 条件所需的查询条件,项目的PM、项目的成员、项目的干系人、项目的白名单
- * Add the query condition for the "I Participate".
- *
- * @param object $stmt
- * @access public
- * @return object
- */
- public function appendInvolvedCondition($stmt)
- {
- return $stmt->andWhere('t2.type')->eq('project')
- ->andWhere('t1.openedBy', true)->eq($this->app->user->account)
- ->orWhere('t1.PM')->eq($this->app->user->account)
- ->orWhere('t2.account')->eq($this->app->user->account)
- ->orWhere('(t3.user')->eq($this->app->user->account)
- ->andWhere('t3.deleted')->eq(0)
- ->markRight(1)
- ->orWhere("CONCAT(',', t1.whitelist, ',')")->like("%,{$this->app->user->account},%")
- ->markRight(1);
- }
- /**
- * 查找项目执行下关联的产品
- * Get linked products with execution under the project.
- *
- * @param array $executionIDs
- *
- * @access protected
- * @return array
- */
- public function getExecutionProductGroup($executionIDs)
- {
- return $this->dao->select('project,product')->from(TABLE_PROJECTPRODUCT)
- ->where('project')->in($executionIDs)
- ->fetchGroup('project', 'product');
- }
- /**
- * 检查用户是否有查看项目的权限。
- * Check the privilege.
- *
- * @param int $projectID
- * @access public
- * @return bool
- */
- public function checkPriv($projectID)
- {
- return !empty($projectID) && ($this->app->user->admin || (strpos(",{$this->app->user->view->projects},", ",{$projectID},") !== false));
- }
- /**
- * 判断项目操作的权限。
- * Judge an action is clickable or not.
- *
- * @param object $project
- * @param string $action
- * @access public
- * @return bool
- */
- public static function isClickable($project, $action)
- {
- global $config;
- $action = strtolower($action);
- if($action == 'publishtemplate') return $project->status == 'wait' || $project->status == 'closed';
- if($action == 'disabletemplate') return $project->status == 'doing';
- if($action == 'deletedeliverable') return !$project->review && empty($project->systemList) && $project->status == 'draft';
- if($action == 'close') return $project->status != 'closed';
- if($action == 'group') return $project->model != 'kanban';
- if($action == 'start') return $project->status == 'wait' || $project->status == 'suspended';
- if($action == 'finish') return $project->status == 'wait' || $project->status == 'doing';
- if($action == 'suspend') return $project->status == 'wait' || $project->status == 'doing';
- if($action == 'activate') return $project->status == 'done' || $project->status == 'closed';
- if($action == 'whitelist') return $project->acl != 'open';
- if($action == 'link') return $project->hasProduct;
- return true;
- }
- /**
- * 检查用户是否可以访问当前项目。
- * Check whether access to the current project is allowed or not.
- *
- * @param int $projectID
- * @param array $projects
- * @access public
- * @return int|false
- */
- public function checkAccess($projectID = 0, $projects = array())
- {
- if(commonModel::isTutorialMode()) return $projectID;
- if(!$projectID)
- {
- if($this->cookie->lastProject) $projectID = $this->cookie->lastProject;
- if(!$projectID) $projectID = $this->session->project ? $this->session->project : (int)key($projects);
- }
- /* 项目模板不校验访问权限。 */
- $isTpl = $this->dao->select('isTpl')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch('isTpl');
- if(empty($isTpl) && !isset($projects[$projectID]))
- {
- if($projectID && strpos(",{$this->app->user->view->projects},", ",{$projectID},") === false && !empty($projects))
- {
- /* Redirect old project to new project. */
- $projectID = $this->dao->select('project')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch('project');
- if(!$projectID || strpos(",{$this->app->user->view->projects},", ",{$projectID},") === false) return false;
- }
- else
- {
- $projectID = key($projects);
- }
- }
- $this->session->set('project', (int)$projectID, $this->app->tab);
- if($projectID) helper::setcookie('lastProject', (string)$projectID);
- return (int)$projectID;
- }
- /**
- * 获取项目的预算单位列表。
- * Get budget unit list.
- *
- * @access public
- * @return array
- */
- public function getBudgetUnitList()
- {
- $budgetUnitList = array();
- if($this->config->vision != 'lite')
- {
- foreach(explode(',', $this->config->project->unitList) as $unit) $budgetUnitList[$unit] = zget($this->lang->project->currencySymbol, $unit, '') . ' ' . zget($this->lang->project->unitList, $unit, '');
- }
- return $budgetUnitList;
- }
- /**
- * 获取有迭代项目关联的产品列表。
- * Get Multiple linked products for project.
- *
- * @param int $projectID
- * @access public
- * @return array
- */
- public function getMultiLinkedProducts($projectID)
- {
- $linkedProducts = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->fetchPairs();
- return $this->dao->select('t3.id,t3.name')->from(TABLE_PROJECTPRODUCT)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
- ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id')
- ->where('t1.product')->in($linkedProducts)
- ->andWhere('t1.project')->ne($projectID)
- ->andWhere('t2.type')->eq('project')
- ->andWhere('t2.deleted')->eq('0')
- ->andWhere('t3.deleted')->eq('0')
- ->fetchPairs('id', 'name');
- }
- /**
- * 根据项目ID获取项目信息。
- * Get a project by id.
- *
- * @param int $projectID
- * @param string $type
- * @access public
- * @return object|false
- */
- public function getByID($projectID, $type = '')
- {
- /* Using demo data during tutorials. */
- if(commonModel::isTutorialMode()) return empty($projectID) ? false : $this->loadModel('tutorial')->getProject();
- /* Get project info. */
- $project = $this->projectTao->fetchProjectInfo($projectID, $type);
- if(!$project) return false;
- /* Replace image url. */
- $project = $this->loadModel('file')->replaceImgURL($project, 'desc');
- return $project;
- }
- /**
- * 通过影子产品ID获取一条项目记录。
- * Get a project by its shadow product.
- *
- * @param int $productID
- * @access public
- * @return object|false
- */
- public function getByShadowProduct($productID)
- {
- return $this->dao->select('t2.*')->from(TABLE_PROJECTPRODUCT)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
- ->where('t1.product')->eq($productID)
- ->andWhere('t2.type')->eq('project')
- ->limit(1)
- ->fetch();
- }
- /**
- * 根据状态和和我参与的查询项目列表。
- * Get project list by status and with my participation.
- *
- * @param string $status
- * @param string $orderBy
- * @param bool $involved
- * @param object|null $pager
- * @access public
- * @return array
- */
- public function getList($status = 'undone', $orderBy = 'order_desc', $involved = false, $pager = null)
- {
- /* Get project list by status. */
- $projects = $this->projectTao->fetchProjectList($status, $orderBy, $involved, $pager);
- if(empty($projects)) return array();
- /* Get team members and estimates under the project. */
- $projectIdList = array_keys($projects);
- $teamCount = $this->projectTao->fetchMemberCountByIdList($projectIdList);
- $estimates = $this->projectTao->fetchTaskEstimateByIdList($projectIdList, 'estimate');
- /* Set project attribute. */
- $this->app->loadClass('pager', true);
- foreach($projects as $projectID => $project)
- {
- $orderBy = in_array($project->model, array('waterfall', 'ipd')) ? 'id_asc' : 'id_desc';
- $pager = in_array($project->model, array('waterfall', 'ipd')) ? null : new pager(0, 1, 1);
- $project->executions = $this->loadModel('execution')->getStatData($projectID, 'undone', 0, 0, false, '', $orderBy, $pager);
- $project->teamCount = isset($teamCount[$projectID]) ? $teamCount[$projectID] : 0;
- $project->estimate = isset($estimates[$projectID]) ? round((float)$estimates[$projectID]->estimate, 2) : 0;
- $project->parentName = $project->parent ? $this->projectTao->getParentProgram($project->path, $project->grade) : '';
- }
- return $projects;
- }
- /**
- * 获取项目列表区块的数据。
- * Get project list for block.
- *
- * @param string $status
- * @param int $projectID
- * @param string $orderBy
- * @param int $limit
- * @param string $excludedModel
- * @access public
- * @return object[]
- */
- public function getOverviewList($status = '', $projectID = 0, $orderBy = 'id_desc', $limit = 10, $excludedModel = '')
- {
- /* Get project list by query. */
- $projects = $this->projectTao->fetchProjectListByQuery($status, $projectID, $orderBy, $limit, $excludedModel);
- if(empty($projects)) return array();
- if($projectID)
- {
- if(empty($projects[$projectID])) return array();
- $projects = array($projectID => $projects[$projectID]);
- }
- /* Get bug, task and story summary under the project. */
- $projectIdList = array_keys($projects);
- $bugSummary = $this->projectTao->getTotalBugByProject($projectIdList);
- $taskSummary = $this->projectTao->getTotalTaskByProject($projectIdList);
- $storySummary = $this->projectTao->getTotalStoriesByProject($projectIdList);
- /* Set project attribute. */
- $today = helper::today();
- foreach($projects as $projectID => $project)
- {
- $project->leftBugs = isset($bugSummary[$projectID]) ? $bugSummary[$projectID]->leftBugs : 0;
- $project->allBugs = isset($bugSummary[$projectID]) ? $bugSummary[$projectID]->allBugs : 0;
- $project->doneBugs = isset($bugSummary[$projectID]) ? $bugSummary[$projectID]->doneBugs : 0;
- $project->allStories = isset($storySummary[$projectID]) ? $storySummary[$projectID]->allStories : 0;
- $project->doneStories = isset($storySummary[$projectID]) ? $storySummary[$projectID]->doneStories : 0;
- $project->leftStories = isset($storySummary[$projectID]) ? $storySummary[$projectID]->leftStories : 0;
- $project->leftTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->leftTasks : 0;
- $project->allTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->allTasks : 0;
- $project->waitTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->waitTasks : 0;
- $project->doingTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->doingTasks : 0;
- $project->rndDoneTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->doneTasks : 0;
- $project->liteDoneTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->litedoneTasks : 0;
- /* Judge whether the project is delayed. */
- if($project->status != 'done' && $project->status != 'closed' && $project->status != 'suspended')
- {
- $delay = helper::diffDate($today, $project->end);
- if($delay > 0) $project->delay = $delay;
- }
- }
- return $projects;
- }
- /**
- * 获取瀑布项目的进度。
- * Get waterfall project progress.
- *
- * @param array $projectIdList
- * @param string $mode waterfall|research
- * @access public
- * @return array
- */
- public function getWaterfallProgress($projectIdList, $mode = 'waterfall')
- {
- /* Get stage list. */
- $stageGroup = $this->dao->select('t1.*')->from(TABLE_EXECUTION)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
- ->where('t1.type')->in('stage')
- ->andWhere('t1.deleted')->eq('0')
- ->andWhere('t1.vision')->eq($this->config->vision)
- ->andWhere('t1.project')->in($projectIdList)
- ->andWhere('t2.model')->eq($mode)
- ->fetchGroup('project', 'id');
- /* Get hours information. */
- $totalHour = $this->dao->select("t1.project, t1.execution, ROUND(SUM(if(t1.status !='closed' && t1.status !='cancel', t1.`left`, 0)), 2) AS totalLeft, ROUND(SUM(t1.`consumed`), 1) AS totalConsumed")->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.execution = t2.id')
- ->where('t2.project')->in(array_keys($stageGroup))
- ->andWhere('t1.deleted')->eq(0)
- ->andWhere('t1.parent')->lt(1)
- ->groupBy('t1.project,t1.execution')
- ->fetchGroup('project', 'execution');
- /* Compute waterfall project progress. */
- $progressList = array();
- foreach($stageGroup as $projectID => $stageList)
- {
- $projectConsumed = 0;
- $projectLeft = 0;
- foreach($stageList as $stageID => $stage)
- {
- if($stage->project != $projectID) continue;
- $projectConsumed += isset($totalHour[$projectID][$stageID]) ? (float)$totalHour[$projectID][$stageID]->totalConsumed : 0;
- $projectLeft += isset($totalHour[$projectID][$stageID]) ? round((float)$totalHour[$projectID][$stageID]->totalLeft, 1) : 0;
- }
- $progressList[$projectID] = ($projectConsumed + $projectLeft) == 0 ? 0 : floor($projectConsumed / ($projectConsumed + $projectLeft) * 1000) / 1000 * 100;
- }
- return $progressList;
- }
- /**
- * 获取瀑布项目的计划值、挣值和实际成本。
- * Get waterfall general PV, EV and AC.
- *
- * @param int $projectID
- * @access public
- * @return array
- */
- public function getWaterfallPVEVAC($projectID)
- {
- $executions = $this->dao->select('id,begin,end,realEnd,status')->from(TABLE_EXECUTION)->where('deleted')->eq(0)->andWhere('vision')->eq($this->config->vision)->andWhere('project')->eq($projectID)->fetchAll('id');
- $stmt = $this->dao->select('id,status,estimate,consumed,`left`,closedReason')->from(TABLE_TASK)->where('execution')->in(array_keys($executions))->andWhere("isParent")->eq(0)->andWhere("deleted")->eq(0)->andWhere('status')->ne('cancel')->query();
- $PV = 0;
- $EV = 0;
- $left = 0;
- while($task = $stmt->fetch())
- {
- $PV += $task->estimate;
- $left += $task->left;
- if($task->status == 'done' or $task->closedReason == 'done')
- {
- $EV += $task->estimate;
- }
- else
- {
- $task->progress = 0;
- if(($task->consumed + $task->left) > 0) $task->progress = round($task->consumed / ($task->consumed + $task->left) * 100, 2);
- $EV += round($task->estimate * $task->progress / 100, 2);
- }
- }
- $AC = $this->dao->select('SUM(consumed) as consumed')->from(TABLE_EFFORT)
- ->where('deleted')->eq(0)
- ->andWhere('project')->eq($projectID)
- ->fetch('consumed');
- if(is_null($AC)) $AC = 0;
- return array('PV' => sprintf("%.2f", $PV), 'EV' => sprintf("%.2f", $EV), 'AC' => sprintf("%.2f", $AC), 'left' => sprintf("%.2f", $left));
- }
- /**
- * 获取项目的工时信息。
- * Get project workhour info.
- *
- * @param int $projectID
- * @access public
- * @return object
- */
- public function getWorkhour($projectID)
- {
- $totalEstimate = $this->dao->select('ROUND(SUM(t1.estimate), 1) AS totalEstimate')->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.execution = t2.id')
- ->where('t2.project')->in($projectID)
- ->andWhere('t2.deleted')->eq(0)
- ->andWhere('t1.deleted')->eq(0)
- ->andWhere('t1.parent')->lt(1)
- ->fetch('totalEstimate');
- $totalConsumed = $this->dao->select('ROUND(SUM(t1.consumed), 1) AS totalConsumed')->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.execution = t2.id')
- ->where('t2.project')->in($projectID)
- ->andWhere('t2.deleted')->eq(0)
- ->andWhere('t1.deleted')->eq(0)
- ->andWhere('t1.parent')->lt(1)
- ->fetch('totalConsumed');
- $totalLeft = $this->dao->select('ROUND(SUM(t1.`left`), 1) AS totalLeft')->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.execution = t2.id')
- ->where('t2.project')->in($projectID)
- ->andWhere('t2.deleted')->eq(0)
- ->andWhere('t1.deleted')->eq(0)
- ->andWhere('t1.parent')->lt(1)
- ->andWhere('t1.status')->ne('closed,cancel')
- ->fetch('totalLeft');
- $workhour = new stdclass();
- $workhour->totalHours = $this->dao->select('sum(t1.days * t1.hours) AS totalHours')->from(TABLE_TEAM)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.root=t2.id')
- ->leftJoin(TABLE_USER)->alias('t3')->on('t1.account=t3.account')
- ->where('t2.id')->in($projectID)
- ->andWhere('t2.deleted')->eq(0)
- ->andWhere('t1.type')->eq('project')
- ->andWhere('t3.deleted')->eq(0)
- ->fetch('totalHours');
- $workhour->totalHours = empty($workhour->totalHours) ? 0 : $workhour->totalHours;
- $workhour->totalEstimate = empty($totalEstimate) ? 0 : $totalEstimate;
- $workhour->totalConsumed = empty($totalConsumed) ? 0 : $totalConsumed;
- $workhour->totalLeft = empty($totalLeft) ? 0 : $totalLeft;
- return $workhour;
- }
- /**
- * 获取给定项目的总计消耗工时。
- * Get projects consumed info.
- *
- * @param array $projectID
- * @param string $time
- * @access public
- * @return object[]
- * @param mixed[] $projectIdList
- */
- public function getProjectsConsumed($projectIdList, $time = '')
- {
- $totalConsumeds = $this->dao->select('t2.project,ROUND(SUM(t1.consumed), 1) AS totalConsumed')->from(TABLE_EFFORT)->alias('t1')
- ->leftJoin(TABLE_TASK)->alias('t2')->on("t1.objectID=t2.id and t1.objectType = 'task'")
- ->where('t2.project')->in($projectIdList)
- ->andWhere('t2.deleted')->eq(0)
- ->andWhere('t2.parent')->lt(1)
- ->beginIF($time == 'THIS_YEAR')->andWhere('LEFT(t1.`date`, 4)')->eq(date('Y'))->fi()
- ->groupBy('t2.project')
- ->fetchAll('project');
- $projects = array();
- foreach($projectIdList as $projectID)
- {
- $project = new stdClass();
- $project->totalConsumed = isset($totalConsumeds[$projectID]->totalConsumed) ? $totalConsumeds[$projectID]->totalConsumed : 0;
- $projects[$projectID] = $project;
- }
- return $projects;
- }
- /**
- * 生成项目下拉框跳转链接
- * Create the link from module,method.
- *
- * @param string $module
- * @param string $method
- * @param int $projectID
- * @access public
- * @return string
- * @param string $extra
- */
- public function getProjectLink($module, $method, $projectID, $extra = '')
- {
- $link = helper::createLink('project', 'index', "projectID=%s");
- $project = $this->projectTao->fetchProjectInfo($projectID);
- if(empty($project->multiple)) return $link;
- if($module == 'build' && $method !== 'create')
- {
- $fromModule = $this->app->tab == 'project' ? 'projectbuild' : 'project';
- $fromMethod = $this->app->tab == 'project' ? 'browse' : 'build';
- return helper::createLink($fromModule, $fromMethod, "projectID=%s");
- }
- if(!empty($this->config->project->linkMap->$module[$method]))
- {
- $linkParams = $this->config->project->linkMap->$module[$method];
- if(!$linkParams[0]) $linkParams[0] = $module;
- if(!$linkParams[1]) $linkParams[1] = $method;
- if(strpos($linkParams[2], '{extra}') !== false) $linkParams[2] = str_replace('{extra}', $extra, $linkParams[2]);
- return helper::createLink($linkParams[0], $linkParams[1], $linkParams[2]) . $linkParams[3];
- }
- if(!empty($this->config->project->linkMap->$module['']))
- {
- $linkParams = $this->config->project->linkMap->$module[''];
- if(!$linkParams[0]) $linkParams[0] = $module;
- return helper::createLink($linkParams[0], $linkParams[1], $linkParams[2]) . $linkParams[3];
- }
- if($this->config->edition != 'open')
- {
- $flow = $this->loadModel('workflow')->getByModule($module);
- if(!empty($flow->app) && in_array($flow->app, array('scrum', 'waterfall', 'kanbanProject'))) $flow->app = 'project';
- if(!empty($flow) && $flow->buildin == '0') return helper::createLink('flow', 'ajaxSwitchBelong', "objectID=%s&moduleName=$module") . "#app=$flow->app";
- }
- if(in_array($module, $this->config->waterfallModules)) return helper::createLink($module, 'browse', "projectID=%s");
- return $link;
- }
- /**
- * Get project stat data .
- *
- * @param int $projectID
- * @access public
- * @return object
- */
- public function getStatData($projectID)
- {
- $executions = $this->loadModel('execution')->getPairs($projectID);
- $storyCount = $this->dao->select('count(t2.story) as storyCount')->from(TABLE_STORY)->alias('t1')
- ->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id = t2.story')
- ->where('t2.project')->eq($projectID)
- ->andWhere('t1.deleted')->eq(0)
- ->fetch('storyCount');
- $bugCount = $this->dao->select('count(id) as bugCount')->from(TABLE_BUG)
- ->where('project')->in($projectID)
- ->andWhere('deleted')->eq(0)
- ->fetch('bugCount');
- $taskCount = $this->dao->select("COUNT(1) AS `count`,
- SUM(CASE WHEN `status` = 'wait' THEN 1 ELSE 0 END) AS `waitCount`,
- SUM(CASE WHEN `status` = 'doing' THEN 1 ELSE 0 END) AS `doingCount`,
- SUM(CASE WHEN `finishedBy` != '' THEN 1 ELSE 0 END) AS `finishedCount`")
- ->from(TABLE_TASK)
- ->where('execution')->in(array_keys($executions))
- ->andWhere('deleted')->eq('0')
- ->fetch();
- $delayedCount = $this->dao->select('COUNT(id) AS `count`')->from(TABLE_TASK)
- ->where('execution')->in(array_keys($executions))
- ->andWhere('deadline')->notZeroDate()
- ->andWhere('deadline')->lt(helper::today())
- ->andWhere('status')->in('wait,doing')
- ->andWhere('deleted')->eq(0)
- ->fetch('count');
- $statData = new stdclass();
- $statData->storyCount = $storyCount;
- $statData->bugCount = $bugCount;
- $statData->taskCount = $taskCount->count;
- $statData->waitCount = $taskCount->waitCount;
- $statData->doingCount = $taskCount->doingCount;
- $statData->finishedCount = $taskCount->finishedCount;
- $statData->delayedCount = $delayedCount;
- return $statData;
- }
- /**
- * Get project pairs.
- *
- * @param bool $ignoreVision
- * @param string $params
- * @access public
- * @return object
- */
- public function getPairs($ignoreVision = false, $params = '')
- {
- return $this->dao->select('id, name')->from(TABLE_PROJECT)
- ->where('type')->eq('project')
- ->andWhere('deleted')->eq(0)
- ->beginIF(!$ignoreVision)->andWhere('vision')->eq($this->config->vision)->fi()
- ->beginIF(strpos($params, 'noproduct') !== false)->andWhere('hasProduct')->eq(0)->fi()
- ->beginIF(strpos($params, 'noclosed') !== false)->andWhere('status')->ne('closed')->fi()
- ->beginIF(strpos($params, 'nosprint') !== false)->andWhere('multiple')->eq('0')->fi()
- ->beginIF(strpos($params, 'multiple') !== false)->andWhere('multiple')->eq('1')->fi()
- ->beginIF(strpos($params, 'nokanban') !== false)->andWhere('model')->ne('kanban')->fi()
- ->beginIF(!$this->app->user->admin && strpos($params, 'haspriv') !== false)->andWhere('id')->in($this->app->user->view->projects)->fi()
- ->fetchPairs();
- }
- /**
- * Get project pairs by programID.
- *
- * @param int $programID
- * @param string $status all|wait|doing|suspended|closed|noclosed|noprogram
- * @param bool $isQueryAll
- * @param string $orderBy
- * @param string $excludedModel
- * @param string|array $model
- * @param string $param multiple|product
- * @access public
- * @return array
- */
- public function getPairsByProgram($programID = 0, $status = 'all', $isQueryAll = false, $orderBy = 'order_asc', $excludedModel = '', $model = '', $param = '')
- {
- if(commonModel::isTutorialMode()) return $this->loadModel('tutorial')->getProjectPairs();
- return $this->dao->select('id, name')->from(TABLE_PROJECT)
- ->where('type')->eq('project')
- ->andWhere('deleted')->eq(0)
- ->andWhere('vision')->eq($this->config->vision)
- ->beginIF(!empty($programID))->andWhere('path')->like("%,$programID,%")->fi()
- ->beginIF($programID === 0 && $status == 'noprogram')->andWhere('parent')->eq(0)->fi()
- ->beginIF($status != 'all' && $status != 'noclosed')->andWhere('status')->eq($status)->fi()
- ->beginIF($excludedModel)->andWhere('model')->ne($excludedModel)->fi()
- ->beginIF($model)->andWhere('model')->in($model)->fi()
- ->beginIF(strpos($param, 'multiple') !== false)->andWhere('multiple')->eq(1)->fi()
- ->beginIF(strpos($param, 'product') !== false)->andWhere('hasProduct')->eq(1)->fi()
- ->beginIF($status == 'noclosed')->andWhere('status')->ne('closed')->fi()
- ->beginIF(!$this->app->user->admin && !$isQueryAll)->andWhere('id')->in($this->app->user->view->projects)->fi()
- ->orderBy($orderBy)
- ->fetchPairs();
- }
- /**
- * 获取关联产品的项目列表信息。
- * Get product list information for linked products.
- *
- * @param array $productIdList
- * @param string $status
- * @access public
- * @return array
- */
- public function getGroupByProduct($productIdList = array(), $status = '')
- {
- return $this->dao->select('t1.product,t2.*')->from(TABLE_PROJECTPRODUCT)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
- ->where('t2.deleted')->eq(0)
- ->andWhere('t2.type')->eq('project')
- ->beginIF(!empty($productIdList))->andWhere('t1.product')->in($productIdList)->fi()
- ->beginIF(!empty($status))->andWhere('t2.status')->in($status)->fi()
- ->beginIF(!$this->app->user->admin)->andWhere('t2.id')->in($this->app->user->view->projects)->fi()
- ->beginIF($this->config->vision)->andWhere('vision')->eq($this->config->vision)->fi()
- ->fetchGroup('product');
- }
- /**
- * Get all the projects under the program set to which an project belongs.
- *
- * @param object $project
- * @access public
- * @return void
- */
- public function getBrotherProjects($project)
- {
- if($project->parent == 0) return array($project->id => $project->id);
- $projectIds = array_filter(explode(',', $project->path));
- $parentProgram = $this->dao->select('*')->from(TABLE_PROGRAM)
- ->where('id')->in($projectIds)
- ->andWhere('`type`')->eq('program')
- ->orderBy('grade desc')
- ->fetch();
- return $this->dao->select('id')->from(TABLE_PROJECT)
- ->where('type')->eq('project')
- ->andWhere('deleted')->eq(0)
- ->andWhere("CONCAT(',', path, ',')")->like("%,{$parentProgram->id},%")
- ->fetchPairs('id');
- }
- /**
- * Get project by id list.
- *
- * @param array $projectIdList
- * @param string $mode all
- * @access public
- * @return object
- */
- public function getByIdList($projectIdList = array(), $mode = '')
- {
- return $this->dao->select('*')->from(TABLE_PROJECT)
- ->where('type')->eq('project')
- ->andWhere('id')->in($projectIdList)
- ->beginIF(!$this->app->user->admin and $mode != 'all')->andWhere('id')->in($this->app->user->view->projects)->fi()
- ->fetchAll('id');
- }
- /**
- * Get project pairs by id list.
- *
- * @param array $projectIdList
- * @param string $model
- * @param string $param
- * @access public
- * @return array
- */
- public function getPairsByIdList($projectIdList = array(), $model = '', $param = '')
- {
- return $this->dao->select('id, name')->from(TABLE_PROJECT)
- ->where('type')->eq('project')
- ->andWhere('deleted')->eq(0)
- ->beginIF($projectIdList)->andWhere('id')->in($projectIdList)->fi()
- ->beginIF(!$this->app->user->admin and $model != 'all')->andWhere('id')->in($this->app->user->view->projects)->fi()
- ->beginIF($model != 'all' and !empty($model))->andWhere('model')->in($model)->fi()
- ->beginIF(strpos($param, 'multiple') !== false)->andWhere('multiple')->eq('1')->fi()
- ->fetchPairs('id', 'name');
- }
- /**
- * 根据项目ID获取关联产品及分支
- * Get branches by project id.
- *
- * @param int $projectID
- *
- * @access public
- * @return array
- */
- public function getBranchesByProject($projectID)
- {
- if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBranchesByProject();
- return $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)
- ->where('project')->eq($projectID)
- ->fetchGroup('product', 'branch');
- }
- /**
- * 根据项目ID获取分支分组。
- * Get branch groups.
- *
- * @param int $projectID
- * @param array $productIdList
- * @access public
- * @return array
- */
- public function getBranchGroup($projectID, $productIdList)
- {
- return $this->dao->select('t1.product as productID, t1.branch as branchID, t2.*')
- ->from(TABLE_PROJECTPRODUCT)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
- ->where('t1.product')->in($productIdList)
- ->andWhere('t1.project')->eq($projectID)
- ->andWhere('t2.deleted')->eq(0)
- ->fetchGroup('productID', 'branchID');
- }
- /**
- * Get No product project|execution List.
- *
- * @access public
- * @return array
- */
- public function getNoProductList()
- {
- return $this->dao->select('t1.product, t2.*')->from(TABLE_PROJECTPRODUCT)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
- ->where('t2.hasProduct')->eq(0)
- ->andWhere('t2.deleted')->eq(0)
- ->fetchAll('id');
- }
- /**
- * 获取项目与执行的键值对
- * Get project and execution pairs.
- *
- * @param string $multiple
- * @param string $status
- * @access public
- * @return array
- */
- public function getProjectExecutionPairs($multiple = '0', $status = 'all')
- {
- return $this->dao->select('project, id')->from(TABLE_PROJECT)
- ->where('deleted')->eq('0')
- ->andWhere('multiple')->eq($multiple)
- ->andWhere('type')->in('sprint,stage,kanban')
- ->beginIF(!in_array($status, array('all', 'undone')))->andWhere('status')->eq($status)->fi()
- ->beginIF($status == 'undone')->andWhere('status')->notIN('done,closed')->fi()
- ->beginIF($this->config->vision)->andWhere("CONCAT(',', vision, ',')")->like("%,{$this->config->vision},%")->fi()
- ->fetchPairs();
- }
- /**
- * 根据状态和项目模型获取项目列表。
- * Get project list by query.
- *
- * @param string $status
- * @param string $order
- * @param int $limit
- * @param string $excludedModel
- * @access public
- * @return void
- */
- public function getProjectList($status, $order, $limit, $excludedModel)
- {
- if(common::isTutorialMode()) return $this->loadModel('tutorial')->getProjectStats();
- return $this->projectTao->fetchProjectListByQuery($status, 0, $order, $limit, $excludedModel);
- }
- /**
- * 获取项目下的执行数据。
- * Get execution data under the project.
- *
- * @param array $projectIdList
- * @access public
- * @return object[]
- */
- public function getExecutionList($projectIdList = array())
- {
- return $this->dao->select('*')->from(TABLE_EXECUTION)
- ->where('deleted')->eq(0)
- ->andWhere('project')->in($projectIdList)
- ->andWhere('type')->in('sprint,stage,kanban')
- ->fetchAll('id');
- }
- /**
- * 根据项目类型生成权限数据。
- * Get project priv data according by the project type.
- *
- * @param string $model scrum|waterfall|noSprint|agileplus|waterfallplus
- * @param int $projectID
- * @param int $hasProduct
- * @access public
- * @return object|false
- */
- public function getPrivsByModel($model = 'waterfall', $projectID = 0, $hasProduct = 0)
- {
- if(!isset($this->config->programPriv->$model)) return false;
- if($projectID) $project = $this->fetchByID($projectID);
- if($model == 'noSprint') $this->config->project->includedPriv = $this->config->project->noSprintPriv;
- if(!$hasProduct) $this->config->project->includedPriv = array_merge($this->config->project->includedPriv, $this->config->project->noProductPriv);
- if(isset($project->coverExecutionPriv) && empty($project->coverExecutionPriv)) $this->config->project->includedPriv = $this->config->project->projectPriv;
- $hasBaseline = true;
- $hasAuditplan = true;
- $hasProcess = true;
- $hasDeliverable = true;
- $hasChange = true;
- if($this->config->edition != 'open' && $projectID)
- {
- $hasBaseline = $this->loadModel('workflowgroup')->hasFeature((int)$project->workflowGroup, 'cm');
- $hasDeliverable = $this->workflowgroup->hasFeature((int)$project->workflowGroup, 'deliverable');
- $hasChange = $this->workflowgroup->hasFeature((int)$project->workflowGroup, 'change');
- $hasAuditplan = $this->workflowgroup->hasFeature((int)$project->workflowGroup, 'auditplan');
- $hasProcess = $this->workflowgroup->hasFeature((int)$project->workflowGroup, 'process');
- }
- $this->app->loadLang('group');
- $privs = new stdclass();
- foreach($this->lang->resource as $module => $methods)
- {
- if(empty($methods)) continue;
- if(!in_array($module, $this->config->programPriv->$model)) continue;
- if($module == 'cm' && !$hasBaseline) continue;
- if($module == 'auditplan' && !$hasAuditplan) continue;
- if($module == 'pssp' && !$hasProcess) continue;
- if($module == 'projectchange' && !$hasChange) continue;
- if($module == 'review' && !$hasDeliverable) continue;
- if($module == 'reviewissue' && !$hasDeliverable) continue;
- if($module == 'review')
- {
- if(!$hasDeliverable) unset($methods->submitDeliverable);
- if(!$hasChange) unset($methods->submitProjectchange);
- if(!$hasBaseline) unset($methods->submitBaseline);
- if(!empty($project) && $project->model == 'scrum') unset($methods->submitIpd, $methods->submitProjectchange, $methods->submitBaseline);
- }
- if($module == 'project' && !$hasDeliverable) unset($methods->deliverable);
- foreach($methods as $method => $label)
- {
- if($method == 'submitIpd' && $model != 'ipd') continue;
- if(isset($this->config->project->includedPriv[$module]) and !in_array($method, $this->config->project->includedPriv[$module])) continue;
- if(!isset($privs->$module)) $privs->$module = new stdclass();
- $privs->$module->$method = $label;
- }
- }
- return $privs;
- }
- /*
- * 构造项目搜索表单配置项。
- * Build search form.
- *
- * @param int $queryID
- * @param string $actionURL
- *
- * @return void
- * */
- /**
- * @param int $queryID
- * @param string $actionURL
- */
- public function buildSearchForm($queryID, $actionURL)
- {
- $this->config->project->search['queryID'] = $queryID;
- $this->config->project->search['actionURL'] = $actionURL;
- $statusList = $this->lang->project->statusList;
- unset($statusList['delay']);
- $this->config->project->search['params']['status']['values'] = $statusList;
- $programPairs = array(0 => '');
- $programPairs += $this->loadModel('program')->getPairs();
- $this->config->project->search['params']['parent']['values'] = $programPairs;
- if($this->config->edition != 'open') $this->config->project->search['params']['workflowGroup']['values'] = $this->loadModel('workflowGroup')->getPairs('project', 'all');
- if(!isset($this->config->setCode) or $this->config->setCode == 0) unset($this->config->project->search['fields']['code'], $this->config->project->search['params']['code']);
- if($this->config->systemMode == 'light') unset($this->config->project->search['fields']['parent'], $this->config->project->search['params']['parent']);
- $this->loadModel('search')->setSearchParams($this->config->project->search);
- }
- /**
- * 构造项目版本的搜索表单配置。
- * Build project build search form.
- *
- * @param array $products
- * @param int $queryID
- * @param int $projectID
- * @param int $productID
- * @param string $type project|execution
- * @access public
- * @return bool
- */
- public function buildProjectBuildSearchForm($products, $queryID, $projectID, $productID, $type = 'project')
- {
- /* Set search param. */
- $project = $this->projectTao->fetchProjectInfo($projectID);
- if(!$project) return false;
- $this->loadModel('build');
- if(!$project->hasProduct) unset($this->config->build->search['fields']['product']);
- $product = $productID ? $this->loadModel('product')->getByID($productID) : '';
- if($product && $product->type != 'normal')
- {
- $this->loadModel('branch');
- $branches = array(BRANCH_MAIN => $this->lang->branch->main) + $this->branch->getPairs($product->id, '', $projectID);
- $this->config->build->search['fields']['branch'] = sprintf($this->lang->build->branchName, $this->lang->product->branchName[$product->type]);
- $this->config->build->search['params']['branch'] = array('operator' => '=', 'control' => 'select', 'values' => $branches);
- }
- /* If there is an execution, set the execution filter item. */
- if($type == 'project' && $project->multiple)
- {
- $executionPairs = $this->loadModel('execution')->getByProject($project->id, 'all', 0, true, $project->model == 'waterfall');
- $this->config->build->search['fields']['execution'] = zget($this->lang->project->executionList, $project->model);
- $this->config->build->search['params']['execution'] = array('operator' => '=', 'control' => 'select', 'values' => $executionPairs);
- }
- $this->config->build->search['module'] = $type == 'project' ? 'projectBuild' : 'executionBuild';
- $this->config->build->search['actionURL'] = helper::createLink($this->app->rawModule, $this->app->rawMethod, "projectID=$projectID&type=bysearch&queryID=myQueryID");
- $this->config->build->search['queryID'] = (int)$queryID;
- $this->config->build->search['params']['product']['values'] = $products;
- $this->config->build->search['params']['system']['values'] = $this->loadModel('system')->getPairs($queryID ? 0 : (int)$productID, '0');
- $this->loadModel('search')->setSearchParams($this->config->build->search);
- return true;
- }
- /**
- * 根据项目集和模型获取项目列表(列表索引为项目编号)。
- * Get project pairs by model and project.
- *
- * @param string|array $model all|scrum|waterfall|kanban
- * @param string $param noclosed
- * @param int $projectID
- * @param bool $pairs
- * @access public
- * @return array array(projectID => projectName, ...)
- */
- public function getPairsByModel($model = 'all', $param = '', $projectID = 0, $pairs = true)
- {
- if(commonModel::isTutorialMode()) return $this->loadModel('tutorial')->getProjectPairs();
- /* Get project list. */
- $projects = $this->projectTao->fetchProjectListByQuery(strpos($param, 'noclosed') !== false ? 'unclosed' : 'all', $projectID);
- if(is_string($model) && $model == 'agileplus') $model = array('scrum', 'agileplus');
- if(is_string($model) && $model == 'waterfallplus') $model = array('waterfall', 'waterfallplus');
- /* Set first program to the project attribute. */
- $model = is_string($model) && $model == 'all' ? array() : (array)$model;
- $multiple = strpos($param, 'multiple') !== false;
- foreach($projects as $projectID => $project)
- {
- if(($model && !in_array($project->model, $model)) || ($multiple && !$project->multiple))
- {
- unset($projects[$projectID]);
- continue;
- }
- list($programID) = explode(',', trim($project->path, ','));
- $projects[$projectID]->program = $programID;
- }
- $programs = $this->loadModel('program')->getPairsByList(toIntArray(array_column($projects, 'program')));
- /* Sort by project order in the program list. */
- $allProjects = array();
- foreach($programs as $programID => $program) $allProjects[$programID] = array();
- $hasProgram = helper::hasFeature('program');
- foreach($projects as $project)
- {
- $programID = zget($project, 'program', '');
- $projectName = $project->name;
- if($this->config->systemMode == 'ALM' && $hasProgram && $programID != $project->id) $projectName = zget($programs, $programID, '') . ' / ' . $projectName;
- $project->name = $projectName;
- $allProjects[$programID][] = $project;
- }
- $projectPairs = array();
- foreach($allProjects as $programID => $projects)
- {
- foreach($projects as $project) $projectPairs[$project->id] = $pairs ? $project->name : $project;
- }
- return $projectPairs;
- }
- /**
- * 根据项目ID获取需求信息。
- * Get stories by project id.
- *
- * @param int $projectID
- * @access public
- * @return array
- */
- public function getStoriesByProject($projectID = 0)
- {
- return $this->dao->select("t2.product, t2.branch, GROUP_CONCAT(t2.story) as storyIDList")->from(TABLE_STORY)->alias('t1')
- ->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id=t2.story')
- ->where('t1.deleted')->eq(0)
- ->beginIF($projectID)->andWhere('t2.project')->eq($projectID)->fi()
- ->groupBy('t2.product, t2.branch')
- ->fetchGroup('product', 'branch');
- }
- /**
- * 获取项目集列表。
- * Get the program tree of project.
- *
- * @param string $browseType
- * @access public
- * @return array
- */
- public function getProgramTree($browseType)
- {
- /* Get program list. */
- $programsList = $this->dao->select('id,name,parent')->from(TABLE_PROGRAM)
- ->where('deleted')->eq('0')
- ->andWhere('type')->eq('program')
- ->andWhere('status')->ne('closed')
- ->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->programs)->fi()
- ->orderBy('grade desc, `order`')
- ->fetchAll();
- /* Init tree data. */
- $programs = array();
- foreach($programsList as $index => $program)
- {
- $programs[$index] = new stdClass();
- $programs[$index]->id = $program->id;
- $programs[$index]->name = $program->name;
- $programs[$index]->parent = $program->parent;
- $programs[$index]->url = helper::createLink('project', 'browse', "programID={$program->id}&browseType={$browseType}");
- }
- return $programs;
- }
- /**
- * 创建项目后,新增团队成员.
- * Add team members after create a project.
- *
- * @param int $projectID
- * @param object $project
- * @param array $members
- * @access protected
- * @return bool
- */
- protected function addTeamMembers($projectID, $project, $members)
- {
- /* Manage Team of project. */
- array_push($members, $project->PM, $project->openedBy);
- $members = array_unique($members);
- $roles = $this->loadModel('user')->getUserRoles(array_values($members));
- $teamMembers = array();
- $this->loadModel('execution');
- foreach($members as $account)
- {
- if(empty($account)) continue;
- $member = new stdClass();
- $member->root = $projectID;
- $member->type = 'project';
- $member->join = helper::now();
- $member->days = zget($project, 'days', 0);
- $member->hours = $this->config->execution->defaultWorkhours;
- $member->account = $account;
- $member->role = zget($roles, $account, '');
- $teamMembers[$account] = $member;
- }
- $this->execution->addProjectMembers($projectID, $teamMembers);
- return !dao::isError();
- }
- /**
- * 创建一个项目。
- * Create a project.
- *
- * @param object $project
- * @param object $postData
- * @access public
- * @return int|bool
- */
- public function create($project, $postData)
- {
- $project = $this->loadModel('file')->processImgURL($project, $this->config->project->editor->create['id'], $this->post->uid);
- $this->projectTao->doCreate($project);
- if(dao::isError()) return false;
- $projectID = $this->dao->lastInsertId();
- /* Add project whitelist. */
- $whitelist = explode(',', $project->whitelist);
- $this->loadModel('personnel')->updateWhitelist($whitelist, 'project', $projectID);
- $program = $project->parent ? $this->getByID((int)$project->parent) : new stdclass();
- $this->projectTao->createDocLib($projectID, $project, $program);
- $this->addTeamMembers($projectID, $project, array($project->openedBy));
- if(in_array($project->model, array('waterfall', 'waterfallplus', 'ipd'))) $this->projectTao->createMilestoneReport($projectID);
- if($project->hasProduct && empty($postData->rawdata->newProduct))
- {
- $this->updateProducts($projectID);
- /* If $_POST has product name, create it. */
- $linkedProductsCount = $this->projectTao->getLinkedProductsCount($project, $postData->rawdata);
- }
- $needCreateProduct = (!$project->hasProduct || isset($postData->rawdata->newProduct) || empty($linkedProductsCount)) && empty($project->isTpl);
- if($needCreateProduct && !$this->projectTao->createProduct($projectID, $project, $postData, $program)) return false;
- /* Save order. */
- $this->dao->update(TABLE_PROJECT)->set('`order`')->eq($projectID * 5)->where('id')->eq($projectID)->exec();
- $this->file->updateObjectID((string)$this->post->uid, $projectID, 'project');
- $this->loadModel('program')->setTreePath($projectID);
- /* Add project admin. */
- if(empty($project->isTpl)) $this->projectTao->addProjectAdmin($projectID);
- if($project->acl != 'open') $this->loadModel('user')->updateUserView(array($projectID), 'project');
- if(empty($project->multiple) and $project->model != 'waterfall' and $project->model != 'waterfallplus') $this->loadModel('execution')->createDefaultSprint($projectID);
- return $projectID;
- }
- /**
- * 检查输入的$product和$branch变量是否合规。
- * Check branch and product valid by project.
- *
- * @param int $parent
- * @param array $products
- * @param array $branch
- * @access public
- * @return bool
- */
- public function checkBranchAndProduct($parent, $products, $branch)
- {
- $topProgramID = $this->loadModel('program')->getTopByID($parent);
- $multipleProducts = $this->loadModel('product')->getMultiBranchPairs((int)$topProgramID);
- foreach($products as $index => $productID)
- {
- if(isset($multipleProducts[$productID]))
- {
- foreach($branch[$index] as $branchID)
- {
- if($branchID == '')
- {
- dao::$errors["branch[{$index}][]"] = $this->lang->project->error->emptyBranch;
- }
- }
- }
- }
- return !dao::isError();
- }
- /**
- * 检查执行的起止日期是否小于项目的起止日期。
- * Check if execution's start and end dates in project's start and end dates.
- *
- * @param int $projectID
- * @param object $project
- * @access public
- * @return bool
- */
- public function checkDates($projectID, $project)
- {
- $executionsCount = $this->dao->select('COUNT(1) AS count')->from(TABLE_PROJECT)
- ->where('project') ->eq($projectID)
- ->andWhere('deleted') ->eq('0')
- ->fetch('count');
- if(empty($executionsCount))return true;
- $maxExecutionEnd = $this->dao->select('`end` as maxEnd')->from(TABLE_PROJECT)
- ->where('project')->eq($projectID)
- ->andWhere('deleted')->eq('0')
- ->orderBy('end_desc')
- ->fetch();
- $minExecutionBegin = $this->dao->select('`begin` as minBegin')->from(TABLE_PROJECT)
- ->where('project')->eq($projectID)
- ->andWhere('deleted')->eq('0')
- ->orderBy('begin_asc')
- ->fetch();
- if($maxExecutionEnd && $project->end < $maxExecutionEnd->maxEnd) dao::$errors['end'] = sprintf($this->lang->project->endGreatEqualExecution, $maxExecutionEnd->maxEnd);
- if($minExecutionBegin && $project->begin > $minExecutionBegin->minBegin) dao::$errors['begin'] = sprintf($this->lang->project->beginLessEqualExecution, $minExecutionBegin->minBegin);
- return !dao::isError();
- }
- /**
- * 更新项目关联的团队成员列表。
- * update teammembers while update project.
- *
- * @param object $project
- * @param object $oldProject
- * @param array $newMembers
- * @access public
- * @return bool
- */
- public function updateTeamMembers($project, $oldProject, $newMembers = array())
- {
- /* Get old project's team and roles. */
- $projectID = (int)$oldProject->id;
- $oldMembers = $this->loadModel('user')->getTeamMemberPairs($projectID, 'project');
- if(empty($newMembers)) $newMembers = array_keys($oldMembers);
- array_push($newMembers, $project->PM);
- $newMembers = array_unique($newMembers);
- $roles = $this->loadModel('user')->getUserRoles(array_values($newMembers));
- /* Delete members while old model is kanban. */
- if($oldProject->model == 'kanban')
- {
- $deleteMembers = array_diff(array_keys($oldMembers), array_values($newMembers));
- $this->projectTao->deleteMembers($projectID, $oldProject->openedBy, $deleteMembers);
- }
- /* Init member default for update members. */
- $member = new stdclass();
- $member->type = 'project';
- $member->root = $projectID;
- $member->join = helper::today();
- $member->days = zget($project, 'days', 0);
- $member->hours = $this->config->execution->defaultWorkhours;
- /* Prepare $addMembers for addProjectMembers(). */
- $addMembers = array();
- foreach($newMembers as $account)
- {
- if(empty($account) or isset($oldMembers[$account])) continue;
- $member->account = $account;
- $member->role = zget($roles, $account, '');
- $addMembers[$account] = $member;
- }
- /* Add members. */
- if(!empty(count($addMembers))) $this->loadModel('execution')->addProjectMembers($projectID, $addMembers);
- if(!isset($_POST['teamMembers'])) $_POST['teamMembers'] = $newMembers;
- return !dao::isError();
- }
- /**
- * 更新此项目下或影子产品下的白名单列表。
- * Update whitelist by project.
- *
- * @param object $project
- * @param object $oldProject
- * @access public
- * @return bool
- */
- public function updateWhitelist($project, $oldProject)
- {
- /* 对比新旧白名单检查是否需要更新白名单。*/
- /* Check if whitelist shoud update .*/
- $projectID = $oldProject->id;
- $whitelist = array_filter(explode(',', (string)$project->whitelist));
- $oldWhitelist = array_filter(explode(',', (string)$oldProject->whitelist));
- if(count($oldWhitelist) != count($whitelist) || !empty(array_diff($oldWhitelist, $whitelist)))
- {
- if(!$oldProject->hasProduct)
- {
- $linkedProducts = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)
- ->where('project')->eq($projectID)
- ->fetchPairs();
- $this->loadModel('personnel')->updateWhitelist($whitelist, 'product', current($linkedProducts));
- }
- $this->loadModel('personnel')->updateWhitelist($whitelist, 'project', $projectID);
- }
- return !dao::isError();
- }
- /**
- * 更新用户视图。
- * Update user view.
- *
- * @param int $projectID
- * @param string $acl
- * @access public
- * @return bool
- */
- public function updateUserView($projectID, $acl)
- {
- if($acl == 'open') return true;
- $this->loadModel('user')->updateUserView(array($projectID), 'project');
- $executions = $this->dao->select('id')->from(TABLE_EXECUTION)
- ->where('project')->eq($projectID)
- ->fetchPairs();
- if($executions) $this->user->updateUserView($executions, 'sprint');
- return !dao::isError();
- }
- /**
- * 更新项目下的所有产品的阶段。
- * Update product stage by project.
- *
- * @param int $projectID
- * @param object $postProductData
- * @access public
- * @return bool
- */
- public function updateProductStage($projectID, $postProductData = null)
- {
- if(empty($postProductData)) return false;
- $stageParis = $this->loadModel('execution')->getPairs($projectID);
- $stageProducts = $this->dao->select('project, product')->from(TABLE_PROJECTPRODUCT)
- ->where('project')->in(array_keys($stageParis))
- ->fetchPairs('project', 'product');
- $products = zget($postProductData, 'products', array());
- $branches = zget($postProductData, 'branch', array(0));
- $plans = zget($postProductData, 'plans', array());
- $productBranches = array();
- foreach($products as $i => $productID) $productBranches[$productID] = zget($branches, $i, array(0));
- foreach($stageProducts as $stageID => $productID)
- {
- foreach($productBranches[$productID] as $branchID)
- {
- $data = new stdclass();
- $data->project = (int)$stageID;
- $data->product = (int)$productID;
- $data->branch = (int)$branchID;
- $data->plan = isset($plans[$productID]) ? implode(',', $plans[$productID]) : '';
- $data->plan = trim($data->plan, ',');
- $data->plan = empty($data->plan) ? 0 : ",$data->plan,";
- $this->dao->replace(TABLE_PROJECTPRODUCT)->data($data)->exec();
- }
- }
- return !dao::isError();
- }
- /**
- * 更新项目。
- * Update project.
- *
- * @param object $project
- * @param object $oldProject
- * @param object $postProductData
- * @access public
- * @return array|false
- */
- public function update($project, $oldProject, $postProductData = null)
- {
- /* 通过主键查老项目信息, 处理父节点和图片字段。*/
- /* Fetch old project's info and dispose parent and file info. */
- $projectID = $oldProject->id;
- if(!isset($project->parent)) $project->parent = $oldProject->parent;
- $project = $this->loadModel('file')->processImgURL($project, $this->config->project->editor->edit['id'], $this->post->uid);
- /* 若此项目为多迭代项目, 检查起止日期不得小于迭代的起止日期。*/
- /* If this project has multiple stage, check if execution's start and end dates in project's start and end dates. */
- if($oldProject->multiple && !$this->checkDates($projectID, $project)) return false;
- /* 如果没有传入项目管理方式,则用之前的管理方式。*/
- /* If no project management method is passed, the project management method is used. */
- if(empty($project->model)) $project->model = $oldProject->model;
- if($project->model != $oldProject->model && !$this->checkCanChangeModel($projectID, $oldProject->model)) $project->model = $oldProject->model;
- /* 更新项目表。*/
- /* Update project table. */
- if(!$this->projectTao->doUpdate($projectID, $project)) return false;
- /* 更新项目的关联信息。*/
- /* Update relation info of this project. */
- $this->updateUserView($projectID, $project->acl); // 更新用户视图。
- $this->updateShadowProduct($project, $oldProject); // 更新影子产品关联信息。
- $this->updateWhitelist($project, $oldProject); // 更新关联的白名单列表。
- $this->updatePlans($projectID, (array)$this->post->plans); // 更新关联的计划列表。
- if($oldProject->hasProduct > 0) $this->updateProducts($projectID, (array)$this->post->products, $postProductData); // 更新关联的产品列表。
- $this->updateTeamMembers($project, $oldProject, zget($_POST, 'teamMembers', array())); // 更新关联的用户信息。
- if($oldProject->stageBy == 'product' && in_array($oldProject->model, array('waterfall', 'waterfallplus'))) $this->updateProductStage($projectID, $postProductData); // 更新多套阶段
- $this->file->updateObjectID((string)$this->post->uid, $projectID, 'project'); // 通过uid更新文件id。
- if($oldProject->parent != $project->parent) $this->loadModel('program')->processNode($projectID, (int)$project->parent, $oldProject->path, $oldProject->grade); // 更新项目从属路径。
- if($oldProject->storyType != $project->storyType)
- {
- /* 编辑项目时如果取消关联需求类型,则把对应类型的需求移除。 */
- $unlinkType = array_diff(explode(',', $oldProject->storyType), explode(',', $project->storyType));
- if($unlinkType) $this->unlinkStoryByType($projectID, $unlinkType);
- }
- if(empty($oldProject->multiple) and !in_array($oldProject->model, array('waterfall', 'waterfallplus'))) $this->loadModel('execution')->syncNoMultipleSprint($projectID); // 无迭代的非瀑布项目需要更新。
- if(in_array($this->config->edition, array('max', 'ipd')) && $oldProject->workflowGroup != $project->workflowGroup) $this->modifyWorkflowGroup($projectID, $oldProject->workflowGroup, $project->workflowGroup); // 更新项目流程。
- if($oldProject->model != $project->model && !in_array($oldProject->model, array('waterfall', 'waterfallplus', 'ipd')) && in_array($project->model, array('waterfall', 'waterfallplus', 'ipd')))
- {
- $this->projectTao->createMilestoneReport($projectID);
- }
- if(dao::isError()) return false;
- return common::createChanges($oldProject, $project);
- }
- /**
- * 批量更新项目。
- * Batch update projects.
- *
- * @param array $data
- * @access public
- * @return array|false
- */
- public function batchUpdate($data)
- {
- $projects = array();
- $allChanges = array();
- $oldProjects = $this->getByIdList(array_keys($data));
- $projects = $this->projectTao->buildBatchUpdateProjects($data, $oldProjects);
- $this->loadModel('execution');
- $this->lang->error->unique = $this->lang->error->repeat;
- foreach($projects as $projectID => $project)
- {
- $oldProject = $oldProjects[$projectID];
- $this->projectTao->doUpdate($projectID, $project);
- if(dao::isError())
- {
- $errors = dao::getError();
- foreach($errors as $key => $error) dao::$errors[$key][0] = 'ID' . $projectID . $error[0];
- return false;
- }
- if(!dao::isError())
- {
- /* 无产品项目信息变更后更新影子产品的相关字段. */
- if(!$oldProject->hasProduct and ($oldProject->name != $project->name or $oldProject->parent != $project->parent or $oldProject->acl != $project->acl)) $this->updateShadowProduct($project, $oldProject);
- if(isset($project->parent))
- {
- $linkedProducts = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->fetchPairs();
- $this->updateProductProgram($oldProject->parent, $project->parent, $linkedProducts);
- if($oldProject->parent != $project->parent) $this->loadModel('program')->processNode($projectID, $project->parent, $oldProject->path, $oldProject->grade);
- }
- /* When acl is open, white list set empty. When acl is private,update user view. */
- if($project->acl == 'open') $this->loadModel('personnel')->updateWhitelist(array(), 'project', $projectID);
- if($project->acl != 'open') $this->loadModel('user')->updateUserView(array($projectID), 'project');
- $this->executeHooks($projectID);
- if(empty($oldProject->multiple) and $oldProject->model != 'waterfall') $this->execution->syncNoMultipleSprint($projectID);
- }
- $allChanges[$projectID] = common::createChanges($oldProject, $project);
- }
- return $allChanges;
- }
- /**
- * 开始项目并更改其状态.
- * start and update a project.
- *
- * @param int $projectID
- * @param object $postData
- * @access public
- * @return array|false
- */
- public function start($projectID, $postData)
- {
- $oldProject = $this->getById($projectID);
- $project = $this->loadModel('file')->processImgURL($postData, $this->config->project->editor->start['id'], $this->post->uid);
- $this->projectTao->doStart($projectID, $project);
- /* When it has multiple errors, only the first one is prompted */
- if(dao::isError())
- {
- if(count(dao::$errors['realBegan']) > 1) dao::$errors['realBegan'] = dao::$errors['realBegan'][0];
- return false;
- }
- $this->recordFirstEnd($projectID);
- if(!$oldProject->multiple) $this->projectTao->changeExecutionStatus($projectID, 'start');
- return common::createChanges($oldProject, $project);
- }
- /**
- * Suspend project and update status.
- * 暂停项目并更改其状态
- *
- * @param int $projectID
- * @param object $project
- * @param string $type
- *
- * @access public
- * @return array|flase
- */
- public function suspend($projectID, $project, $type = 'project')
- {
- $editorIdList = $this->config->project->editor->suspend['id'];
- $oldProject = $this->getById($projectID, $type);
- $project = $this->loadModel('file')->processImgURL($project, $editorIdList, $this->post->uid);
- $this->projectTao->doSuspend($projectID, $project);
- if(!$oldProject->multiple) $this->projectTao->changeExecutionStatus($projectID, 'suspend');
- return common::createChanges($oldProject, $project);
- }
- /**
- * Activate project.
- *
- * @param int $projectID
- * @param object $project
- * @access public
- * @return array $changes|false
- */
- public function activate($projectID, $project)
- {
- if($project->begin > $project->end)
- {
- dao::$errors['end'] = $this->lang->project->error->endLessBegin;
- return false;
- }
- $oldProject = $this->projectTao->fetchProjectInfo($projectID);
- $daoSuccess = $this->projectTao->doActivate($projectID, $project);
- if(!$daoSuccess) return false;
- if(empty($oldProject->multiple) and $oldProject->model != 'waterfall') $this->loadModel('execution')->syncNoMultipleSprint($projectID);
- /* Update start and end date of tasks in this project. */
- if($project->readjustTask)
- {
- $tasks = $this->projectTao->fetchUndoneTasks($projectID);
- $this->projectTao->updateTasksStartAndEndDate($tasks, $oldProject, $project);
- }
- /* Activate the shadow product of the project. (only change product status) */
- if(!$oldProject->hasProduct)
- {
- $productID = $this->loadModel('product')->getProductIDByProject($projectID);
- $product = new stdclass();
- $product->status = $this->config->vision == 'or' ? 'wait' : 'normal';
- $this->product->activate($productID, $product);
- }
- return common::createChanges($oldProject, $project);
- }
- /**
- * 关闭项目并更改其状态
- * Close project and update status.
- *
- * @param int $projectID
- * @param object $project
- *
- * @access public
- * @return array|false
- */
- public function close($projectID, $project)
- {
- $oldProject = $this->getByID($projectID);
- $editorIdList = $this->config->project->editor->close['id'];
- $project = $this->loadModel('file')->processImgURL($project, $editorIdList, $this->post->uid);
- $this->projectTao->doClosed($projectID, $project, $oldProject);
- /* When it has multiple errors, only the first one is prompted */
- if(dao::isError())
- {
- if(count(dao::$errors['realEnd']) > 1) dao::$errors['realEnd'] = dao::$errors['realEnd'][0];
- return false;
- }
- if(!$oldProject->multiple) $this->projectTao->changeExecutionStatus($projectID, 'close');
- /* Close the shadow product of the project. */
- if(!$oldProject->hasProduct)
- {
- $productID = $this->loadModel('product')->getProductIDByProject($projectID);
- $product = new stdclass();
- $product->status = 'closed';
- $this->product->close($productID, $product);
- }
- $this->loadModel('score')->create('project', 'close', $oldProject);
- return common::createChanges($oldProject, $project);
- }
- /**
- * 如果是无产品项目,更新影子产品信息。
- * Update shadow product.
- *
- * @param object $project
- * @param object $oldProject
- * @access public
- * @return bool
- */
- public function updateShadowProduct($project, $oldProject)
- {
- /* If this is a project without product, update shadow product's info. */
- if($oldProject->hasProduct) return true;
- /* If oldProject has no product and name or parent or acl has changed, update shadow product. */
- if($oldProject->name != $project->name || $oldProject->parent != $project->parent || $oldProject->acl != $project->acl)
- {
- $product = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($oldProject->id)->fetch('product');
- $topProgram = !empty($project->parent) ? $this->loadModel('program')->getTopByID((int)$project->parent) : 0;
- /* Convert program acl to custom for product table */
- $productAcl = $project->acl;
- if($productAcl == 'program') $productAcl = 'private';
- $this->dao->update(TABLE_PRODUCT)
- ->set('name')->eq($project->name)
- ->set('program')->eq($topProgram)
- ->set('acl')->eq($productAcl)
- ->where('id')->eq($product)
- ->exec();
- }
- /* Update shadow product's status if need .*/
- if(isset($project->status) && $oldProject->status != $project->status && strpos('doing,closed', $project->status) !== false)
- {
- $productID = $this->loadModel('product')->getProductIDByProject($oldProject->id);
- if($project->status == 'doing') $this->product->activate($productID);
- if($project->status == 'closed') $this->product->close($productID);
- }
- return !dao::isError();
- }
- /**
- * 删除项目下关联执行与产品与文档库
- * Deletes related items under project.
- *
- * @param string $table product|execution|doclib
- * @param int|array $idList
- *
- * @access public
- * @return bool
- */
- public function deleteByTableName($table, $idList)
- {
- if(strpos($table, 'doclib') !== false)
- {
- $this->dao->update($table)->set('deleted')->eq(1)->where('execution')->eq($idList)->exec();
- }
- else
- {
- $this->dao->update($table)->set('deleted')->eq(1)->where('id')->in($idList)->exec();
- }
- return !dao::isError();
- }
- /**
- * 更新产品的项目集。
- * Update the program of the product.
- *
- * @param int $oldProgram
- * @param int $newProgram
- * @param array $products
- * @access public
- * @return bool
- */
- public function updateProductProgram($oldProgram, $newProgram, $products)
- {
- /* Product belonging project set processing. */
- $oldTopProgram = $this->loadModel('program')->getTopByID($oldProgram);
- $newTopProgram = $this->program->getTopByID($newProgram);
- if($oldTopProgram != $newTopProgram)
- {
- $productList = $this->loadModel('product')->getByIdList($products);
- foreach($products as $productID)
- {
- $product = zget($productList, $productID, array());
- if(!$product) continue;
- unset($product->id);
- unset($product->code);
- if(empty($product->closedDate)) unset($product->closedDate);
- $product->program = $newTopProgram;
- $this->product->update($productID, $product);
- }
- }
- return !dao::isError();
- }
- /**
- * 删除项目和迭代下对应需求概念的关联关系。
- * Unlink the relationship between the project and the story concept under the iteration and the project.
- *
- * @param int $projectID
- * @param string $storyType
- * @access public
- * @return void
- */
- public function unlinkStoryByType($projectID = 0, $storyType = '')
- {
- $idList = $this->dao->select('id')->from(TABLE_PROJECT)
- ->where('deleted')->eq(0)
- ->andWhere('project')->eq($projectID)
- ->fetchPairs();
- $idList[$projectID] = $projectID;
- $storyIdList = $this->dao->select('t1.story')->from(TABLE_PROJECTSTORY)->alias('t1')
- ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
- ->where('t2.type')->in($storyType)
- ->beginIF($projectID > 0)->andWhere('t1.project')->in($idList)->fi()
- ->fetchPairs();
- if($storyIdList) $this->dao->delete()->from(TABLE_PROJECTSTORY)->where('story')->in($storyIdList)->beginIF($projectID > 0)->andWhere('project')->in($idList)->fi()->exec();
- }
- /**
- * 移除项目成员。
- * Unlink a member.
- *
- * @param int $projectID
- * @param string $account
- * @param bool $removeExecution
- * @access public
- * @return bool
- */
- public function unlinkMember($projectID, $account, $removeExecution = false)
- {
- $user = $this->loadModel('user')->getById($account);
- $changes = array(array('field' => 'removeDiff', 'old' => '', 'new' => '', 'diff' => $user->realname));
- $this->projectTao->unlinkTeamMember($projectID, 'project', $account, $user->realname, $changes);
- $this->user->updateUserView(array($projectID), 'project', array($account));
- if($removeExecution)
- {
- $executions = $this->loadModel('execution')->getByProject($projectID, 'undone', 0, true);
- $this->projectTao->unlinkTeamMember(array_keys($executions), 'execution', $account, $user->realname, $changes);
- $this->user->updateUserView(array_keys($executions), 'sprint', array($account));
- }
- $linkedProducts = $this->loadModel('product')->getProductPairsByProject($projectID);
- if(!empty($linkedProducts)) $this->user->updateUserView(array_keys($linkedProducts), 'product', array($account));
- return !dao::isError();
- }
- /**
- * 维护项目团队成员。
- * Manage team members.
- *
- * @param int $projectID
- * @param array $members
- * @access public
- * @return bool
- */
- public function manageMembers($projectID, $members)
- {
- $project = $this->projectTao->fetchProjectInfo($projectID);
- $oldMembers = $this->dao->select('`account`, `join`, `days`')->from(TABLE_TEAM)->where('root')->eq($projectID)->andWhere('type')->eq('project')->fetchAll();
- $oldJoin = array_column($oldMembers, 'join', 'account');
- $oldDays = array_column($oldMembers, 'days', 'account');
- /* Check fields. */
- foreach($members as $key => $member)
- {
- if(empty($member->account)) continue;
- if((float)$member->hours > 24) dao::$errors = $this->lang->project->errorHours;
- if(!empty($project->days))
- {
- if(isset($oldDays[$member->account]) && $oldDays[$member->account] == $member->days) continue;
- if((int)$member->days > $project->days) dao::$errors = sprintf($this->lang->project->daysGreaterProject, $project->days);
- }
- }
- if(dao::isError()) return false;
- $this->dao->delete()->from(TABLE_TEAM)->where('root')->eq($projectID)->andWhere('type')->eq('project')->exec();
- $accounts = $this->projectTao->insertMember($members, $projectID, $oldJoin);
- $this->projectTao->updateMemberView($projectID, $accounts, $oldJoin);
- if(empty($project->multiple) and $project->model != 'waterfall') $this->loadModel('execution')->syncNoMultipleSprint($projectID);
- return !dao::isError();
- }
- /**
- * 将数字转换成带单位的数字。
- * Convert budget unit.
- *
- * @param float|string $budget
- * @access public
- * @return float|string $projectBudget
- */
- public function getBudgetWithUnit($budget)
- {
- $budget = (float)$budget;
- $rawBudget = $budget;
- if($budget < $this->config->project->budget->tenThousand)
- {
- $budget = round($budget, $this->config->project->budget->precision);
- $unit = '';
- }
- elseif($budget < $this->config->project->budget->oneHundredMillion && $budget >= $this->config->project->budget->tenThousand)
- {
- $budget = round($budget/$this->config->project->budget->tenThousand, $this->config->project->budget->precision);
- $unit = $this->lang->project->tenThousand;
- }
- else
- {
- $budget = round($budget/$this->config->project->budget->oneHundredMillion, $this->config->project->budget->precision);
- $unit = $this->lang->project->hundredMillion;
- }
- return !commonModel::checkNotCN() ? $budget . $unit : round($rawBudget, $this->config->project->budget->precision);
- }
- /**
- * 更新项目关联的产品信息。
- * Update products of a project.
- *
- * @param int $projectID
- * @param array $products
- * @param object|array|null $postProductData
- * @access public
- * @return bool
- */
- public function updateProducts($projectID, $products = array(), $postProductData = array())
- {
- if(!empty($_POST['newProduct'])) return array();
- $this->loadModel('user');
- $teams = array_keys($this->getTeamMembers($projectID));
- $stakeholders = array_keys($this->loadModel('stakeholder')->getStakeHolderPairs($projectID));
- $members = array_merge($teams, $stakeholders);
- /* Link products of other programs. */
- if(!empty($_POST['otherProducts'])) return $this->linkOtherProducts($projectID, $members);
- /* Link products of current program of the project. */
- $products = isset($_POST['products']) ? (array)$_POST['products'] : $products;
- $oldProjectProducts = $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->fetchGroup('product', 'branch');
- $this->linkProducts($projectID, $products, $oldProjectProducts, $members);
- /* Delete the execution linked products that is not linked with the execution. */
- if($projectID > 0)
- {
- $executions = $this->dao->select('id')->from(TABLE_EXECUTION)->where('project')->eq($projectID)->fetchPairs('id');
- $this->dao->delete()->from(TABLE_PROJECTPRODUCT)->where('project')->in($executions)->andWhere('product')->notin(toIntArray($products))->exec();
- if(isset($_POST['stageBy']) and $_POST['stageBy'] == 'product')
- {
- $this->dao->update(TABLE_PROJECT)->set('stageBy')->eq('product')->where('id')->eq($projectID)->orWhere('project')->eq($projectID)->exec();
- }
- $project = $this->projectTao->fetchProjectInfo($projectID);
- if(!empty($project) && !empty($executions) && $project->stageBy == 'project' && in_array($project->model, array('waterfall', 'waterfallplus', 'ipd')))
- {
- $this->loadModel('execution');
- unset($postProductData->plans);
- foreach($executions as $executionID) $this->execution->updateProducts($executionID, $postProductData);
- }
- }
- /* Update the user product view. */
- $oldProductIdList = array_keys($oldProjectProducts);
- $needUpdate = array_merge(array_diff($oldProductIdList, $products), array_diff($products, $oldProductIdList));
- if($needUpdate) $this->user->updateUserView($needUpdate, 'product', $members);
- /* Create actions. */
- $this->loadModel('action');
- if(!empty($needUpdate)) $this->action->create('project', $projectID, 'managed', '', implode(',', $products));
- /* 如果有取消关联的产品,且项目有迭代且是非瀑布项目,记录关联产品执行到action表。*/
- /* If there are unlinkedProducts and it is multiple project and it isn't waterfall project, record to table action. */
- $unlinkedProducts = array_diff($oldProductIdList, $products);
- if(!empty($unlinkedProducts) && !empty($project) && $project->multiple && $project->model != 'waterfall' && $project->model != 'waterfallplus')
- {
- $products = $this->dao->select('name')->from(TABLE_PRODUCT)
- ->where('id')->in($unlinkedProducts)
- ->fetchPairs();
- $this->action->create('project', $projectID, 'unlinkproduct', '', implode(',', $products));
- }
- return !dao::isError();
- }
- /**
- * 关联项目所属项目集下的产品。
- * Link products of current program of the project.
- *
- * @param int $projectID
- * @param array $products
- * @param array $oldProjectProducts
- * @param array $members
- * @access public
- * @return bool
- */
- public function linkProducts($projectID, $products, $oldProjectProducts, $members)
- {
- $this->loadModel('user');
- /* Delete the linked data. */
- $this->dao->delete()->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->exec();
- /* Update the user product view. */
- if(empty($products))
- {
- $this->user->updateUserView(array_keys($oldProjectProducts), 'product', $members);
- return true;
- }
- /* Set the product information linked with the project. */
- $branches = isset($_POST['branch']) ? $_POST['branch'] : array();
- $plans = isset($_POST['plans']) ? $_POST['plans'] : array();
- $charter = isset($_POST['charter']) ? $_POST['charter'] : 0;
- $roadmaps = isset($_POST['roadmaps']) ? $_POST['roadmaps'] : array();
- $existedProducts = array();
- foreach($products as $index => $productID)
- {
- if(empty($productID)) continue;
- if(!isset($existedProducts[$productID])) $existedProducts[$productID] = array();
- $oldPlan = 0;
- $oldRoadmap = 0;
- $branch = isset($branches[$index]) ? $branches[$index] : 0;
- $branch = !is_array($branch) ? array($branch => $branch) : $branch;
- foreach($branch as $branchID)
- {
- if(isset($existedProducts[$productID][$branchID])) continue;
- if(isset($oldProjectProducts[$productID][$branchID]))
- {
- $oldProjectProduct = $oldProjectProducts[$productID][$branchID];
- if($this->app->rawMethod != 'edit') $oldPlan = $oldProjectProduct->plan;
- }
- $data = new stdclass();
- $data->project = $projectID;
- $data->product = $productID;
- $data->branch = (int)$branchID;
- $data->plan = (isset($plans[$productID]) && !empty($plans[$productID])) ? implode(',', $plans[$productID]) : $oldPlan;
- $data->plan = trim((string)$data->plan, ',');
- $data->plan = empty($data->plan) ? 0 : ",$data->plan,";
- if($charter)
- {
- $data->roadmap = (isset($roadmaps[$productID]) && !empty($roadmaps[$productID])) ? implode(',', $roadmaps[$productID]) : $oldRoadmap;
- $data->roadmap = trim((string)$data->roadmap, ',');
- $data->roadmap = empty($data->roadmap) ? 0 : ",$data->roadmap,";
- }
- $this->dao->insert(TABLE_PROJECTPRODUCT)->data($data)->exec();
- $existedProducts[$productID][$branchID] = true;
- }
- }
- return true;
- }
- /**
- * 关联其他项目集下的产品。
- * Link products of other programs.
- *
- * @access public
- * @return bool
- * @param int $projectID
- * @param mixed[] $members
- */
- public function linkOtherProducts($projectID, $members)
- {
- $this->loadModel('user');
- $productIdList = array();
- $otherProducts = $_POST['otherProducts'];
- foreach($otherProducts as $otherProduct)
- {
- if(!$otherProduct) continue;
- $data = new stdclass();
- $data->project = $projectID;
- $data->plan = 0;
- if(strpos($otherProduct, '_') !== false)
- {
- $params = explode('_', $otherProduct);
- $data->product = $params[0];
- $data->branch = $params[1];
- }
- else
- {
- $data->product = $otherProduct;
- $data->branch = 0;
- }
- $this->dao->replace(TABLE_PROJECTPRODUCT)->data($data)->exec();
- $productIdList[] = $data->product;
- }
- $this->user->updateUserView($productIdList, 'product', $members);
- if($projectID > 0 and isset($_POST['stageBy']) and $_POST['stageBy'] == 'product')
- {
- $this->dao->update(TABLE_PROJECT)->set('stageBy')->eq('product')->where('id')->eq($projectID)->orWhere('project')->eq($projectID)->exec();
- }
- return !dao::isError();
- }
- /**
- * 更新关联的产品和执行的用户视图。
- * Update userview for involved product and execution.
- *
- * @param int $projectID
- * @param array $users
- * @access public
- * @return bool
- */
- public function updateInvolvedUserView($projectID, $users = array())
- {
- $products = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->fetchPairs('product', 'product');
- $this->loadModel('user')->updateUserView($products, 'product', $users);
- $executions = $this->dao->select('id')->from(TABLE_EXECUTION)->where('project')->eq($projectID)->fetchPairs('id', 'id');
- if($executions) $this->user->updateUserView($executions, 'sprint', $users);
- return true;
- }
- /**
- * 获取项目团队成员。
- * Get team members.
- *
- * @param int $projectID
- * @access public
- * @return array
- */
- public function getTeamMembers($projectID)
- {
- if(commonModel::isTutorialMode()) return $this->loadModel('tutorial')->getTeamMembers();
- $project = $this->projectTao->fetchProjectInfo($projectID);
- if(empty($project)) return array();
- return $this->dao->select("t1.*, t1.hours * t1.days AS totalHours, t2.id as userID, if(t2.deleted='0', t2.realname, t1.account) as realname")->from(TABLE_TEAM)->alias('t1')
- ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account = t2.account')
- ->where('t1.root')->eq((int)$projectID)
- ->andWhere('t1.type')->eq($project->type)
- ->andWhere('t2.deleted')->eq('0')
- ->beginIF($this->config->vision)->andWhere("CONCAT(',', t2.visions, ',')")->like("%,{$this->config->vision},%")->fi()
- ->fetchAll('account');
- }
- /**
- * Get team member pairs by projectID.
- *
- * @param int $projectID
- * @access public
- * @return array
- */
- public function getTeamMemberPairs($projectID)
- {
- $project = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch();
- if(empty($project)) return array();
- $type = $project->type == 'project' ? 'project' : 'execution';
- $members = $this->dao->select("t1.account, t2.realname")->from(TABLE_TEAM)->alias('t1')
- ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account = t2.account')
- ->where('t1.root')->eq((int)$projectID)
- ->andWhere('t1.type')->eq($type)
- ->andWhere('t2.deleted')->eq('0')
- ->beginIF($this->config->vision)->andWhere("CONCAT(',', t2.visions, ',')")->like("%,{$this->config->vision},%")->fi()
- ->fetchPairs('account', 'realname');
- return $members;
- }
- /**
- * Get team member group.
- *
- * @param mixed $projectIdList
- * @access public
- * @return array
- */
- public function getTeamMemberGroup($projectIdList)
- {
- if(empty($projectIdList)) return array();
- return $this->dao->select("t1.account, t2.realname, t1.root as project")->from(TABLE_TEAM)->alias('t1')
- ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account = t2.account')
- ->where('t1.root')->in($projectIdList)
- ->andWhere('t1.type')->eq('project')
- ->andWhere('t2.deleted')->eq('0')
- ->beginIF($this->config->vision)->andWhere("CONCAT(',', t2.visions, ',')")->like("%,{$this->config->vision},%")->fi()
- ->fetchGroup('project', 'account');
- }
- /**
- * Get members of a project who can be imported.
- *
- * @param int $projectID
- * @param array $currentMembers
- * @access public
- * @return array
- */
- public function getMembers2Import($projectID, $currentMembers)
- {
- if($projectID == 0) return array();
- return $this->dao->select('account, role, hours')
- ->from(TABLE_TEAM)
- ->where('root')->eq($projectID)
- ->andWhere('type')->eq('project')
- ->andWhere('account')->notIN($currentMembers)
- ->fetchAll('account');
- }
- /**
- * Get stats for project kanban.
- *
- * @access public
- * @return array
- */
- public function getStats4Kanban()
- {
- /* Get execution of the status is doing. */
- $executions = $this->loadModel('execution')->getStatData(0, 'doing', 0, 0, false, 'hasParentName|skipParent');
- $projectExecutions = array();
- foreach($executions as $execution)
- {
- if(!empty($execution->projectName)) $execution->projectName = htmlspecialchars_decode($execution->projectName);
- $projectExecutions[$execution->project][$execution->id] = $execution;
- }
- /* The execution is sorted in reverse order by execution ID. */
- $ongoingExecutions = array();
- foreach($projectExecutions as $projectID => $executions)
- {
- krsort($projectExecutions[$projectID]);
- $ongoingExecutions[$projectID] = current($projectExecutions[$projectID]);
- }
- $projectsStats = $this->loadModel('program')->getProjectStats(0, 'all', 0, 'order_asc');
- $projectsStats = $this->projectTao->classifyProjects($projectsStats);
- /* Only display recent two closed projects. */
- $projectsStats = $this->projectTao->sortAndReduceClosedProjects($projectsStats, 2);
- return array($projectsStats, $ongoingExecutions);
- }
- /**
- * 设置项目的导航菜单。
- * Set menu of project module.
- *
- * @param int $projectID
- * @access public
- * @return int|false
- */
- public function setMenu($projectID)
- {
- $moduleName = $this->app->rawModule;
- $methodName = $this->app->rawMethod;
- if(!$this->loadModel('common')->isOpenMethod($moduleName, $methodName) and !commonModel::hasPriv($moduleName, $methodName)) $this->common->deny($moduleName, $methodName, false);
- $projectID = (int)$this->checkAccess($projectID, $this->getPairsByProgram());
- $project = commonModel::isTutorialMode() ? $this->loadModel('tutorial')->getProject() : $this->projectTao->fetchProjectInfo($projectID);
- if(!$project) return false;
- if(!commonModel::isTutorialMode() && $projectID != 0 && !$this->checkPriv($projectID))
- {
- $this->projectTao->accessDenied();
- return true;
- }
- /* Reset project priv. */
- $this->common->resetProjectPriv($projectID);
- /* Set secondary menu. */
- $this->projectTao->setMenuByModel($project->model);
- $this->projectTao->setMenuByProduct($projectID, $project->hasProduct, $project->model);
- $this->lang->switcherMenu = $this->getSwitcher($projectID, $moduleName, $methodName);
- /* Replace url params. */
- common::setMenuVars('project', $projectID);
- $this->setNoMultipleMenu($projectID);
- if($project->acl == 'open') unset($this->lang->project->menu->settings['subMenu']->whitelist);
- if($this->app->getModuleName() == 'repo' || $this->app->getModuleName() == 'mr') $this->loadModel('repo')->setHideMenu($projectID);
- if(!empty($project->isTpl)) dao::$filterTpl = 'never';
- return $projectID;
- }
- /**
- * 设置未启用迭代的菜单。
- * Set multi-scrum menu.
- *
- * @param int $projectID
- * @access public
- * @return bool
- */
- public function setNoMultipleMenu($projectID)
- {
- $this->session->set('multiple', true);
- $project = common::isTutorialMode() ? $this->loadModel('tutorial')->getProject() : $this->projectTao->fetchProjectInfo($projectID);
- if(empty($project) || $project->multiple) return false;
- if(!in_array($project->type, array('project', 'sprint', 'kanban'))) return false;
- if($project->type == 'project')
- {
- $model = $project->model;
- $executionID = $this->loadModel('execution')->getNoMultipleID($projectID);
- }
- else
- {
- $model = $project->type == 'kanban' ? 'kanban' : 'scrum';
- $executionID = $project->id;
- $projectID = $project->project;
- $project = $this->projectTao->fetchProjectInfo($projectID);
- }
- if(empty($projectID) || empty($executionID)) return false;
- $this->session->set('project', $projectID, 'project');
- $this->session->set('multiple', false);
- global $lang;
- $navGroup = zget($lang->navGroup, $this->app->rawModule);
- $lang->$navGroup->menu = $lang->project->noMultiple->{$model}->menu;
- $lang->$navGroup->menuOrder = $lang->project->noMultiple->{$model}->menuOrder;
- $lang->$navGroup->dividerMenu = $lang->project->noMultiple->{$model}->dividerMenu;
- $this->projectTao->setNavGroupMenu($navGroup, $executionID, $project);
- $lang->project->menu = $lang->$navGroup->menu;
- $lang->project->menuOrder = $lang->$navGroup->menuOrder;
- $lang->project->dividerMenu = $lang->$navGroup->dividerMenu;
- $this->lang->switcherMenu = $this->getSwitcher($projectID, $this->app->rawModule, $this->app->rawMethod);
- /* 无迭代项目不开启过程删除导航。 */
- if(in_array($this->config->edition, array('max', 'ipd')))
- {
- $this->loadModel('workflowgroup');
- if(!$this->workflowgroup->hasFeature((int)$project->workflowGroup, 'process')) unset($lang->project->menu->other['dropMenu']->pssp);
- if(!$this->workflowgroup->hasFeature((int)$project->workflowGroup, 'deliverable'))
- {
- unset($lang->project->menu->deliverable);
- unset($lang->project->menu->auditplan['subMenu']->deliverable);
- }
- if(!$this->workflowgroup->hasFeature((int)$project->workflowGroup, 'auditplan')) unset($lang->project->menu->other['dropMenu']->auditplan);
- }
- /* If projectID is set, cannot use homeMenu. */
- unset($lang->project->homeMenu);
- if(empty($project->hasProduct))
- {
- unset($lang->project->menu->settings['subMenu']->products);
- }
- else
- {
- unset($lang->project->menu->settings['subMenu']->module);
- unset($lang->project->menu->projectplan);
- }
- $this->loadModel('common')->resetProjectPriv($projectID);
- return true;
- }
- /**
- * 检查是否可以修改model字段
- * Check if the project model can be changed.
- *
- * @param int $projectID
- * @param string $model
- * @access public
- * @return bool
- */
- public function checkCanChangeModel($projectID, $model)
- {
- if(empty($model)) return false;
- $checkList = $this->config->project->checkList->$model;
- if(in_array($this->config->edition, array('max', 'ipd'))) $checkList = $this->config->project->maxCheckList->$model;
- foreach($checkList as $module)
- {
- if($module == '') continue;
- $type = '';
- $table = constant('TABLE_'. strtoupper($module));
- if($module == 'execution')
- {
- $this->app->loadConfig('execution');
- $type = zget($this->config->execution->modelList, $model, '');
- }
- /* 检查对应类型的数据是否已经存在。 */
- /* Check if the data of the type already exists. */
- $object = $this->getDataByProject($table, $projectID, $type);
- if(!empty($object)) return false;
- }
- return true;
- }
- /**
- * 根据项目ID获取项目关联的对象。
- * Get the objects under the project.
- *
- * @param string $table
- * @param int $projectID
- * @param string $type
- * @access public
- * @return object|bool
- */
- public function getDataByProject($table, $projectID, $type = '')
- {
- return $this->dao->select('id')->from($table)
- ->where('project')->eq($projectID)
- ->beginIF(!empty($type))->andWhere('type')->eq($type)->fi()
- ->fetch();
- }
- /**
- * Add plans.
- *
- * @param int $projectID
- * @param array $plans
- * @access public
- * @return bool
- */
- public function addPlans($projectID, $plans)
- {
- $planIdList = array();
- foreach($plans as $planList)
- {
- if(!$planList) continue;
- foreach($planList as $planID)
- {
- $planIdList[$planID] = $planID;
- }
- }
- $planIdList = toIntArray($planIdList);
- if(empty($planIdList)) return true;
- $planStoryGroup = $this->loadModel('story')->getStoriesByPlanIdList($planIdList);
- foreach($planIdList as $planID)
- {
- $planStories = $planProducts = array();
- $planStory = isset($planStoryGroup[$planID]) ? $planStoryGroup[$planID] : array();
- if(!empty($planStory))
- {
- foreach($planStory as $id => $story)
- {
- if($story->status != 'active')
- {
- unset($planStory[$id]);
- continue;
- }
- $planProducts[$story->id] = $story->product;
- }
- $planStories = array_keys($planStory);
- $this->loadModel('execution')->linkStory($projectID, $planStories);
- }
- }
- return !dao::isError();
- }
- /**
- * 更新项目关联的计划。
- * Update project's plans.
- *
- * @param int $projectID
- * @param array $plans
- * @access public
- * @return bool
- */
- public function updatePlans($projectID, $plans)
- {
- /* Transfer multi dimensional array to one dimensional array. */
- $newPlans = array();
- if(isset($plans))
- {
- foreach($plans as $planList)
- {
- if(is_array($planList))
- foreach($planList as $planID) $newPlans[$planID] = $planID;
- }
- }
- if(empty($newPlans)) return true;
- /* Fetch old plan list. */
- $oldPlanList = $this->dao->select('plan')->from(TABLE_PROJECTPRODUCT)
- ->where('project')->eq($projectID)
- ->andWhere('plan')->ne(0)
- ->fetchPairs();
- $oldPlans = array();
- foreach($oldPlanList as $oldPlanIDList)
- {
- if(is_numeric($oldPlanIDList)) $oldPlans[$oldPlanIDList] = $oldPlanIDList;
- if(!is_numeric($oldPlanIDList))
- {
- $oldPlanIDList = explode(',', $oldPlanIDList);
- foreach($oldPlanIDList as $oldPlanID) $oldPlans[$oldPlanID] = $oldPlanID;
- }
- }
- if(count($newPlans) != count($oldPlans) || !empty(array_diff($newPlans, $oldPlans))) $this->loadModel('productplan')->linkProject($projectID, $newPlans);
- return !dao::isError();
- }
- /**
- * 更新项目排序
- * update project order
- *
- * @param array $idList
- * @param string $orderBy
- *
- * @access public
- * @return bool
- */
- public function updateOrder($idList, $orderBy)
- {
- $projects = $this->dao->select('id,`order`')->from(TABLE_PROJECT)
- ->where('id')->in($idList)
- ->orderBy($orderBy)
- ->fetchPairs('order', 'id');
- foreach($projects as $order => $id)
- {
- $newID = array_shift($idList);
- if($id == $newID) continue;
- $this->dao->update(TABLE_PROJECT)
- ->set('`order`')->eq($order)
- ->set('lastEditedBy')->eq($this->app->user->account)
- ->set('lastEditedDate')->eq(helper::now())
- ->where('id')->eq($newID)
- ->exec();
- }
- return !dao::isError();
- }
- /**
- * 获取项目集的最小开始时间
- * Get program min begin
- *
- * @param int $objectID
- *
- * @access public
- * @return string
- */
- public function getProgramMinBegin($objectID)
- {
- return $this->dao->select('`begin` as minBegin')->from(TABLE_PROGRAM)
- ->where('id')->ne($objectID)
- ->andWhere('deleted')->eq(0)
- ->andWhere('path')->like("%,{$objectID},%")
- ->orderBy('begin_asc')
- ->fetch('minBegin');
- }
- /**
- * 获取项目集的最大结束时间
- * get program max end
- *
- * @param int $objectID
- *
- * @access public
- * @return string
- */
- public function getProgramMaxEnd($objectID)
- {
- return $this->dao->select('`end` as maxEnd')->from(TABLE_PROGRAM)
- ->where('id')->ne($objectID)
- ->andWhere('deleted')->eq(0)
- ->andWhere('path')->like("%,{$objectID},%")
- ->andWhere("`end` is true")
- ->orderBy('end_desc')
- ->fetch('maxEnd');
- }
- /**
- * 获取执行的团队成员。
- * get execution members.
- *
- * @param string $account
- * @param array $executions
- *
- * @access protected
- * @return array
- */
- protected function getExecutionMembers($account, $executions)
- {
- return $this->dao->select('t1.root,t2.name')->from(TABLE_TEAM)->alias('t1')
- ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.root=t2.id')
- ->where('t1.root')->in($executions)
- ->andWhere('t1.type')->eq('execution')
- ->andWhere('t1.account')->eq($account)
- ->fetchPairs();
- }
- /**
- * 根据项目状态和权限生成列表中操作列按钮。
- * Build table action menu for project browse page.
- *
- * @param object $project
- * @access public
- * @return array
- */
- public function buildActionList($project)
- {
- $actions = array();
- /* Set status button. */
- if($project->status == 'wait' || $project->status == 'suspended') $actions[] = 'start';
- if($project->status == 'doing') $actions[] = 'close';
- if($project->status == 'closed') $actions[] = 'active';
- /* A drop-down button to set the status. */
- $canClose = common::hasPriv('project', 'close') && $project->status != 'doing';
- $canActivate = common::hasPriv('project', 'activate') && $project->status != 'closed';
- if(common::hasPriv('project', 'suspend') || $canClose || $canActivate)
- {
- $menu = 'pause';
- if($project->status != 'doing') $menu .= ',close';
- if($project->status != 'closed') $menu .= ',active';
- $actions[] = 'other:' . $menu;
- }
- $actions[] = 'edit';
- $actions[] = 'group';
- if($this->config->vision != 'lite')
- {
- $actions[] = 'perm';
- if(common::hasPriv('project', 'manageProducts') || common::hasPriv('project', 'whitelist') || common::hasPriv('project', 'delete')) $actions[] = 'more:link,whitelist,delete';
- }
- else
- {
- $actions[] = 'whitelist';
- $actions[] = 'delete';
- }
- /* Set whether the button can be clicked. */
- foreach($actions as &$action)
- {
- if(strpos($action, ':'))
- {
- $actionList = explode(':', $action);
- $action = $actionList[0] . ':';
- foreach(explode(',', $actionList[1]) as $actionName)
- {
- if(!$this->isClickable($project, $actionName)) $action .= '-';
- $action .= $actionName . ',';
- }
- continue;
- }
- if(!$this->isClickable($project, $action)) $action = array('name' => $action, 'disabled' => true);
- }
- return $actions;
- }
- /**
- * 格式化要在数据表格打印的数据。
- * Format data for list.
- *
- * @param object $project
- * @param array $PMList
- * @access public
- * @return object
- */
- public function formatDataForList($project, $PMList)
- {
- $projectBudget = $this->getBudgetWithUnit($project->budget);
- $project->budget = $project->budget != 0 ? zget($this->lang->project->currencySymbol, $project->budgetUnit) . ' ' . $projectBudget : $this->lang->project->future;
- $project->statusTitle = $this->processStatus('project', $project);
- $project->estimate = $project->estimate . $this->lang->project->workHourUnit;
- $project->consume = $project->consumed . $this->lang->project->workHourUnit;
- $project->surplus = $project->left . $this->lang->project->workHourUnit;
- $project->progress = $project->progress;
- $project->end = $project->end == LONG_TIME ? $this->lang->project->longTime : $project->end;
- $project->invested = !empty($this->config->execution->defaultWorkhours) ? round($project->consumed / $this->config->execution->defaultWorkhours, 2) : 0;
- if($project->PM)
- {
- $user = zget($PMList, $project->PM, '');
- $project->PM = zget($user, 'realname', $project->PM);
- $project->PMAvatar = zget($user, 'avatar', '');
- $project->PMUserID = zget($user, 'id', 0);
- }
- return $project;
- }
- /**
- * 记录项目启动时的计划完成日期。
- * Record the end date when the project is started.
- *
- * @param int $projectID
- * @access public
- * @return bool
- */
- public function recordFirstEnd($projectID)
- {
- $project = $this->dao->select('end')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch();
- $this->dao->update(TABLE_PROJECT)->set('firstEnd')->eq(helper::isZeroDate($project->end) ? null : $project->end)->where('id')->eq($projectID)->exec();
- return !dao::isError();
- }
- /*
- * 获取旧页面1.5级下拉。
- * Get project swapper.
- *
- * @param int $projectID
- * @param string $currentModule
- * @param string $currentMethod
- * @access public
- * @return string
- */
- /**
- * @param int $projectID
- * @param string $currentModule
- * @param string $currentMethod
- */
- public function getSwitcher($projectID, $currentModule, $currentMethod)
- {
- if($currentModule == 'project' && $currentMethod == 'browse') return '';
- $currentProjectName = $this->lang->project->common;
- if($projectID)
- {
- $currentProject = $this->getById($projectID);
- $currentProjectName = $currentProject->name;
- }
- if($this->app->viewType == 'mhtml' && $projectID)
- {
- $output = $this->lang->project->common . $this->lang->hyphen;
- $output .= "<a id='currentItem' href=\"javascript:showSearchMenu('project', '$projectID', '$currentModule', '$currentMethod', '')\">{$currentProjectName} <span class='icon-caret-down'></span></a><div id='currentItemDropMenu' class='hidden affix enter-from-bottom layer'></div>";
- return $output;
- }
- $this->app->loadConfig('index');
- $dropMenuMethod = in_array("{$this->app->moduleName}-{$this->app->methodName}", $this->config->index->oldPages) ? 'ajaxGetOldDropMenu' : 'ajaxGetDropMenu';
- $dataRide = in_array("{$this->app->moduleName}-{$this->app->methodName}", $this->config->index->oldPages) ? 'searchList' : 'dropmenu' ;
- $dropMenuLink = helper::createLink('project', $dropMenuMethod, "objectID=$projectID&module=$currentModule&method=$currentMethod");
- $output = "<div class='btn-group header-btn' id='swapper'><button data-toggle='dropdown' type='button' class='btn' id='currentItem' title='{$currentProjectName}'><span class='text'>{$currentProjectName}</span> <span class='caret' style='margin-bottom: -1px'></span></button><div id='dropMenu' class='dropdown-menu search-list' data-ride='{$dataRide}' data-url='$dropMenuLink'>";
- $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>';
- $output .= "</div></div>";
- return $output;
- }
- /**
- * 检查阶段是否有相关数据。
- * Check whether the stage has related data.
- *
- * @param int $executionID
- * @access public
- * @return bool
- */
- public function hasStageData($executionID)
- {
- if(empty($executionID)) return false;
- $childStage = $this->dao->select('id')->from(TABLE_EXECUTION)->where('parent')->eq($executionID)->andWhere('type')->in('sprint,stage,kanban')->andWhere('deleted')->eq(0)->limit(1)->fetch();
- if(!empty($childStage)) return false;
- $task = $this->dao->select('id')->from(TABLE_TASK)->where('execution')->eq($executionID)->andWhere('deleted')->eq(0)->limit(1)->fetch();
- if(!empty($task)) return true;
- $effort = $this->dao->select('id')->from(TABLE_EFFORT)->where('execution')->eq($executionID)->andWhere('deleted')->eq(0)->limit(1)->fetch();
- if(!empty($effort)) return true;
- $bug = $this->dao->select('id')->from(TABLE_BUG)->where('execution')->eq($executionID)->andWhere('deleted')->eq(0)->limit(1)->fetch();
- if(!empty($bug)) return true;
- $story = $this->dao->select('t1.id')->from(TABLE_STORY)->alias('t1')
- ->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id=t2.story')
- ->where('t2.project')->eq($executionID)
- ->andWhere('t1.deleted')->eq(0)
- ->limit(1)->fetch();
- if(!empty($story)) return true;
- $case = $this->dao->select('t1.id')->from(TABLE_CASE)->alias('t1')
- ->leftJoin(TABLE_PROJECTCASE)->alias('t2')->on('t1.id=t2.case')
- ->where('t2.project')->eq($executionID)
- ->andWhere('t1.deleted')->eq(0)
- ->limit(1)->fetch();
- if(!empty($case)) return true;
- $build = $this->dao->select('id')->from(TABLE_BUILD)->where('execution')->eq($executionID)->andWhere('deleted')->eq(0)->limit(1)->fetch();
- if(!empty($build)) return true;
- $testtask = $this->dao->select('id')->from(TABLE_TESTTASK)->where('execution')->eq($executionID)->andWhere('deleted')->eq(0)->limit(1)->fetch();
- if(!empty($testtask)) return true;
- $testreport = $this->dao->select('id')->from(TABLE_TESTREPORT)->where('execution')->eq($executionID)->andWhere('deleted')->eq(0)->limit(1)->fetch();
- if(!empty($testreport)) return true;
- $doc = $this->dao->select('id')->from(TABLE_DOC)->where('execution')->eq($executionID)->andWhere('deleted')->eq(0)->limit(1)->fetch();
- if(!empty($doc)) return true;
- $doclib = $this->dao->select('id')->from(TABLE_DOCLIB)->where('execution')->eq($executionID)->andWhere('type')->eq('execution')->andWhere('main')->ne('1')->andWhere('deleted')->eq(0)->limit(1)->fetch();
- if(!empty($doclib)) return true;
- $libID = $this->dao->select('id')->from(TABLE_DOCLIB)->where('type')->eq('execution')->andWhere('execution')->eq($executionID)->andWhere('main')->eq('1')->limit(1)->fetch('id');
- $module = $this->dao->select('id')->from(TABLE_MODULE)
- ->where('deleted')->eq(0)
- ->andWhere('((type')->eq('task')
- ->andWhere('root')->eq($executionID)
- ->markRight(1)
- ->orWhere('(type')->eq('doc')
- ->andWhere('root')->eq($libID)
- ->markRight(2)
- ->limit(1)
- ->fetch();
- if(!empty($module)) return true;
- return false;
- }
- /**
- * 获取瀑布/融合瀑布项目不允许解除关联的产品。
- * Get waterfall/waterfallplus unmodifiable products.
- *
- * @param object $project
- * @param array $linkedProducts
- * @access public
- * @return array
- */
- public function getDisabledProducts($project, $linkedProducts = array())
- {
- $disabledProducts = array();
- if(empty($linkedProducts)) return $disabledProducts;
- if(!in_array($project->model, array('waterfall', 'waterfallplus'))) return $disabledProducts;
- $projectStories = $this->dao->select('t2.product')->from(TABLE_STORY)->alias('t1')
- ->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id=t2.story')
- ->where('t1.deleted')->eq(0)
- ->andWhere('t1.type')->eq('story')
- ->andWhere('t2.project')->eq($project->id)
- ->andWhere('t2.product')->in(array_keys($linkedProducts))
- ->fetchPairs();
- $executionIdSQL = $this->dao->select('id')->from(TABLE_EXECUTION)->where('deleted')->eq('0')->andWhere('project')->eq($project->id)->get();
- $executionProducts = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->subIn($executionIdSQL)->andWhere('product')->in(array_keys($linkedProducts))->fetchPairs();
- $executionStories = array();
- if($project->stageBy == 'project')
- {
- $executionStories = $this->dao->select('t2.product')->from(TABLE_STORY)->alias('t1')
- ->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id=t2.story')
- ->where('t1.deleted')->eq(0)
- ->andWhere('t1.type')->eq('story')
- ->andWhere('t2.project')->subIn($executionIdSQL)
- ->andWhere('t2.product')->in($executionProducts)
- ->fetchPairs();
- }
- foreach($linkedProducts as $productID => $product)
- {
- if($project->stageBy == 'product')
- {
- if(isset($projectStories[$productID]) && !isset($executionProducts[$productID])) $disabledProducts[$productID] = $this->lang->project->disabledHint->linkedStory;
- if(!isset($projectStories[$productID]) && isset($executionProducts[$productID])) $disabledProducts[$productID] = $this->lang->project->disabledHint->createdStage;
- if(isset($projectStories[$productID]) && isset($executionProducts[$productID])) $disabledProducts[$productID] = $this->lang->project->disabledHint->linkedStoryAndStage;
- }
- else
- {
- if(isset($projectStories[$productID]) && !isset($executionStories[$productID])) $disabledProducts[$productID] = $this->lang->project->disabledHint->linkedStory;
- if(isset($projectStories[$productID]) && isset($executionStories[$productID])) $disabledProducts[$productID] = $this->lang->project->disabledHint->linkedStoryAndExecution;
- }
- }
- return $disabledProducts;
- }
- /**
- * 查找项目下是否有冻结的对象。
- * Has frozen object.
- *
- * @param int $projectID
- * @param string $category SRS|PP
- * @return bool
- */
- public function hasFrozenObject($projectID, $category)
- {
- if(empty($projectID) || empty($category)) return false;
- $frozenObject = $this->dao->select('1')->from(TABLE_PROJECTDELIVERABLE)->alias('t1')
- ->leftJoin(TABLE_DELIVERABLE)->alias('t2')->on('t1.deliverable = t2.id')
- ->where('t1.project')->eq($projectID)
- ->andWhere('t1.frozen')->ne('')
- ->andWhere('t2.category')->eq($category)
- ->limit(1)
- ->fetch('1');
- return !empty($frozenObject);
- }
- }
|