| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973 |
- <?php
- /**
- * The model file of task module of ZenTaoPMS.
- *
- * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
- * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
- * @author Chunsheng Wang <chunsheng@cnezsoft.com>
- * @package task
- * @version $Id: model.php 5154 2013-07-16 05:51:02Z chencongzhi520@gmail.com $
- * @link https://www.zentao.net
- */
- class taskModel extends model
- {
- /**
- * 激活任务。
- * Activate task.
- *
- * @param object $task
- * @param string $comment
- * @param object $teamData
- * @param mixed[] $drag
- * @access public
- * @return bool
- */
- public function activate($task, $comment, $teamData, $drag = array())
- {
- $taskID = $task->id;
- if(strpos($this->config->task->activate->requiredFields, 'comment') !== false && !$comment)
- {
- dao::$errors['comment'] = sprintf($this->lang->error->notempty, $this->lang->comment);
- return false;
- }
- $oldTask = $this->getById($taskID);
- if($oldTask->isParent) $this->config->task->activate->requiredFields = '';
- $this->dao->update(TABLE_TASKTEAM)->set('status')->eq('wait')->where('task')->eq($task->id)->andWhere('consumed')->eq(0)->andWhere('left')->gt('0')->exec();
- $this->dao->update(TABLE_TASKTEAM)->set('status')->eq('doing')->where('task')->eq($task->id)->andWhere('consumed')->gt(0)->andWhere('left')->gt(0)->exec();
- $this->dao->update(TABLE_TASKTEAM)->set('status')->eq('done')->where('task')->eq($task->id)->andWhere('consumed')->gt(0)->andWhere('left')->eq('0')->exec();
- if(!empty($oldTask->team))
- {
- /* When activate and assigned to a team member, then update his left data in teamData. */
- $valueCount = array_count_values($teamData->team);
- if(isset($valueCount[$task->assignedTo]) && $valueCount[$task->assignedTo] == 1)
- {
- $teamIndex = zget(array_flip($teamData->team), $task->assignedTo, '');
- $teamData->teamLeft[$teamIndex] = $task->left;
- }
- $this->manageTaskTeam($oldTask->mode, $task, $teamData);
- $task = $this->computeMultipleHours($oldTask, $task);
- if(!empty($task->assignedTo) && $task->assignedTo == 'closed') $task->assignedTo = '';
- }
- if(isset($task->left)) $task->left = round((float)$task->left, 2);
- $this->dao->update(TABLE_TASK)->data($task)
- ->autoCheck()
- ->batchCheck($this->config->task->activate->requiredFields, 'notempty')
- ->checkFlow()
- ->where('id')->eq((int)$taskID)
- ->exec();
- if(dao::isError()) return false;
- if($task->left != $oldTask->left) $this->loadModel('program')->refreshProjectStats($oldTask->project);
- if($oldTask->parent > 0) $this->updateParentStatus($taskID);
- if($oldTask->story) $this->loadModel('story')->setStage($oldTask->story);
- $changes = common::createChanges($oldTask, $task);
- if($this->post->comment != '' || !empty($changes))
- {
- $actionID = $this->loadModel('action')->create('task', $taskID, 'Activated', $this->post->comment);
- $this->action->logHistory($actionID, $changes);
- }
- if($this->config->edition != 'open' && $oldTask->feedback) $this->loadModel('feedback')->updateStatus('task', $oldTask->feedback, $task->status, $oldTask->status, $taskID);
- $this->updateKanbanCell($taskID, $drag, $oldTask->execution);
- return !dao::isError();
- }
- /**
- * 添加一条工时记录。
- * Add task effort.
- *
- * @param object $data
- * @access public
- * @return int
- */
- public function addTaskEffort($data)
- {
- $oldTask = $this->getById($data->task);
- $relation = $this->loadModel('action')->getRelatedFields('task', $data->task);
- $effort = new stdclass();
- $effort->objectType = 'task';
- $effort->objectID = $data->task;
- $effort->execution = $oldTask->execution;
- $effort->product = $relation['product'];
- $effort->project = (int)$relation['project'];
- $effort->account = $data->account;
- $effort->date = $data->date;
- $effort->consumed = round((float)$data->consumed, 2);
- $effort->left = round((float)$data->left, 2);
- $effort->work = isset($data->work) ? $data->work : '';
- $effort->vision = $this->config->vision;
- $effort->order = isset($data->order) ? $data->order : 0;
- $this->dao->insert(TABLE_EFFORT)->data($effort)->autoCheck()->exec();
- $effortID = $this->dao->lastInsertID();
- $this->loadModel('program')->refreshProjectStats($effort->project);
- return $effortID;
- }
- /**
- * 批量创建任务后的其他数据处理。
- * other data process after task batch create.
- *
- * @param array $taskIdList
- * @param object $parent
- * @access public
- * @return bool
- */
- public function afterBatchCreate($taskIdList, $parent = null)
- {
- /* Process other data after split task. */
- if($parent && !empty($taskIdList))
- {
- $this->afterSplitTask($parent, $taskIdList);
- }
- return !dao::isError();
- }
- /**
- * 批量编辑任务后的其他数据处理。
- * other data process after task batch edit.
- *
- * @param object[] $tasks
- * @param object[] $oldTasks
- * @access public
- * @return bool
- */
- public function afterBatchUpdate($tasks, $oldTasks = array())
- {
- $this->loadModel('story');
- $this->loadModel('kanban');
- $this->loadModel('feedback');
- $today = helper::today();
- $currentAccount = $this->app->user->account;
- $syncStatus = false;
- foreach($tasks as $taskID => $task)
- {
- $oldTask = zget($oldTasks, $taskID);
- /* Record effort. */
- if(!empty($task->consumed) && $task->consumed > $oldTask->consumed)
- {
- $record = new stdclass();
- $record->account = $currentAccount;
- $record->task = $taskID;
- $record->date = $today;
- $record->left = $task->left;
- $record->consumed = $task->consumed - $oldTask->consumed;
- $this->addTaskEffort($record);
- }
- /* Update Kanban and story stage. */
- if($oldTask->story) $this->story->setStage($oldTask->story);
- if($task->status != $oldTask->status) $this->kanban->updateLane($oldTask->execution, 'task', $taskID);
- /* Update parent task's status, date and hour. */
- if($oldTask->parent > 0)
- {
- $this->updateParentStatus($taskID);
- $this->computeBeginAndEnd($oldTask->parent);
- }
- if($this->config->edition != 'open' && $oldTask->feedback && !isset($feedbacks[$oldTask->feedback]))
- {
- $feedbacks[$oldTask->feedback] = $oldTask->feedback;
- $this->feedback->updateStatus('task', $oldTask->feedback, $task->status, $oldTask->status, $taskID);
- }
- if(!empty($task->story) && !empty($oldTask->isParent) && $this->post->syncChildren) $this->syncStoryToChildren($task);
- if(!$syncStatus && $oldTask->status == 'wait' && $task->status == 'doing')
- {
- $syncStatus = true;
- $this->loadModel('common')->syncPPEStatus($oldTask->id);
- }
- if($this->config->edition != 'open' && $task->story != $oldTask->story)
- {
- if($oldTask->story > 0)
- {
- $this->dao->delete()->from(TABLE_RELATION)
- ->where('relation')->eq('generated')
- ->andWhere('AID')->eq($oldTask->story)
- ->andWhere('AType')->eq('story')
- ->andWhere('BID')->eq($oldTask->id)
- ->andWhere('BType')->eq('task')
- ->exec();
- }
- if($task->story > 0)
- {
- $relation = new stdClass();
- $relation->relation = 'generated';
- $relation->AID = $task->story;
- $relation->AType = 'story';
- $relation->BID = $oldTask->id;
- $relation->BType = 'task';
- $relation->product = 0;
- $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
- }
- }
- }
- return !dao::isError();
- }
- /**
- * 处理状态变化之后的操作。
- * Process other data after task status changed.
- *
- * @param object $task
- * @param array $changes
- * @param string $action Finished|Closed|Started
- * @param array $output
- * @access public
- * @return bool
- */
- public function afterChangeStatus($task, $changes, $action, $output)
- {
- /* Process other data. */
- if($task->parent > 0) $this->updateParentStatus($task->id);
- if($task->isParent) $this->updateChildrenStatus($task->id, $task->status);
- if($task->story) $this->loadModel('story')->setStage($task->story);
- if(!empty($output)) $this->updateKanbanCell($task->id, $output, $task->execution);
- $files = $this->loadModel('file')->saveUpload('task', $task->id);
- if($changes || $this->post->comment)
- {
- $fileAction = !empty($files) ? $this->lang->addFiles . implode(',', $files) . "\n" : '';
- $actionID = $this->loadModel('action')->create('task', $task->id, $action, $fileAction . $this->post->comment);
- $this->action->logHistory($actionID, $changes);
- }
- return !dao::isError();
- }
- /**
- * 创建任务后的其他数据处理。
- * Other data process after task create.
- *
- * @param object $task
- * @param array $taskIdList
- * @param int $bugID
- * @param int $todoID
- * @access public
- * @return bool
- */
- public function afterCreate($task, $taskIdList, $bugID, $todoID)
- {
- $this->loadModel('file');
- $this->setTaskFiles($taskIdList); // Set attachments for tasks.
- foreach($taskIdList as $taskID)
- {
- /* If the task comes from a bug, update the task and bug information. */
- if($bugID > 0)
- {
- $this->dao->update(TABLE_TASK)->set('fromBug')->eq($bugID)->where('id')->eq($taskID)->exec();
- $this->dao->update(TABLE_BUG)->set('toTask')->eq($taskID)->where('id')->eq($bugID)->exec();
- $this->loadModel('action')->create('bug', $bugID, 'converttotask', '', $taskID);
- }
- /* If the task comes from a todo, update the todo information. */
- if($todoID > 0)
- {
- $this->dao->update(TABLE_TODO)->set('status')->eq('done')->where('id')->eq($todoID)->exec();
- $this->loadModel('action')->create('todo', $todoID, 'finished', '', "TASK:$taskID");
- /* If the todo comes from a feedback, update the feedback information. */
- $todo = $this->dao->findByID($todoID)->from(TABLE_TODO)->fetch();
- if($this->config->edition != 'open' && $todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, 'done', '', $todoID);
- }
- /* If the task comes from a design, update the task information. */
- if(!empty($task->design))
- {
- $design = $this->loadModel('design')->getByID($task->design);
- $this->dao->update(TABLE_TASK)->set('designVersion')->eq($design->version)->where('id')->eq($taskID)->exec();
- }
- /* If the task comes from a story, update the stage of the story. */
- if($task->story) $this->loadModel('story')->setStage($task->story);
- }
- return !dao::isError();
- }
- /**
- * 拆分任务后更新其他数据。
- * Process other data after split task.
- *
- * @param object $oldParentTask
- * @param array $childrenIdList
- * @access public
- * @return bool
- */
- public function afterSplitTask($oldParentTask, $childrenIdList = array())
- {
- $parentID = (int)$oldParentTask->id;
- /* When a normal task is consumed, create the subtask and update the parent task status. */
- if($oldParentTask->isParent == 0 && $oldParentTask->consumed > 0)
- {
- $copyTaskID = $this->taskTao->copyTaskData($oldParentTask);
- if(dao::isError()) return false;
- $this->loadModel('action')->create('task', $copyTaskID, 'Opened', '');
- $childrenIdList = arrayUnion(array($copyTaskID), $childrenIdList);
- }
- $parentTask = new stdclass();
- $parentTask->isParent = 1;
- $parentTask->lastEditedBy = $this->app->user->account;
- $parentTask->lastEditedDate = helper::now();
- $this->dao->update(TABLE_TASK)->data($parentTask)->where('id')->eq($parentID)->exec();
- $this->updateParentStatus(current($childrenIdList));
- $this->computeBeginAndEnd($parentID);
- $this->loadModel('program')->refreshProjectStats($oldParentTask->project);
- /* Create a action. */
- $extra = implode(',', $childrenIdList);
- $actionID = $this->loadModel('action')->create('task', $parentID, 'createChildren', '', trim($extra, ','));
- /* Create a log history. */
- $newParentTask = $this->getByID($parentID);
- $changes = common::createChanges($oldParentTask, $newParentTask);
- if(!empty($changes)) $this->action->logHistory($actionID, $changes);
- return !dao::isError();
- }
- /**
- * 开始任务后的其他数据处理。
- * Other data process after task start.
- *
- * @param object $oldTask
- * @param array $changes
- * @param float $left
- * @param array $output
- * @param string $message
- * @access public
- * @return array|bool
- */
- public function afterStart($oldTask, $changes, $left, $output = array(), $message = '')
- {
- /* Update the data of the parent task. */
- if($oldTask->parent > 0) $this->computeBeginAndEnd($oldTask->parent);
- /* Create related dynamic and record. */
- $action = 'Started';
- if($oldTask->status == 'pause') $action = 'Restarted';
- if($left == 0) $action = 'Finished';
- $this->afterChangeStatus($oldTask, $changes, $action, $output);
- /* Send Webhook notifications and synchronize status to execution, project and program. */
- $oldExecution = $this->loadModel('execution')->fetchByID($oldTask->execution);
- $this->loadModel('common')->syncPPEStatus($oldTask->id);
- /* Remind whether to update status of the bug, if task which from that bug has been finished. */
- if($changes && $this->needUpdateBugStatus($oldTask))
- {
- $response = $this->taskTao->getRemindBugLink($oldTask, $changes);
- if($response) return $response;
- }
- if($oldExecution->status == 'wait')
- {
- $inLiteKanban = $this->config->vision == 'lite' && $this->app->tab == 'project' && $this->session->kanbanview == 'kanban';
- if(($this->app->tab == 'execution' || $inLiteKanban) && $oldExecution->type == 'kanban') return array('result' => 'success', 'message' => $message, 'load' => true);
- }
- return true;
- }
- /**
- * 编辑任务后的其他数据处理:记录分数、更改需求阶段、处理父任务变更、更改反馈状态等。
- * Additional data processing after updating tasks: record scores, change story stage, handle parent task changes, change feedback status.
- *
- * @param object $oldTask
- * @param object $task
- * @access public
- * @return void
- */
- public function afterUpdate($oldTask, $task)
- {
- /* Update children task. */
- if(isset($task->execution) && $task->execution != $oldTask->execution)
- {
- $newExecution = $this->loadModel('execution')->fetchByID((int)$task->execution);
- $task->project = $newExecution->project;
- $this->dao->update(TABLE_TASK)->set('execution')->eq($task->execution)->set('module')->eq($task->module)->set('project')->eq($task->project)->where('parent')->eq($task->id)->exec();
- }
- /* Multi-task change to normal task. */
- if($task->mode == 'single') $this->dao->delete()->from(TABLE_TASKTEAM)->where('task')->eq($task->id)->exec();
- if(isset($task->version) && $task->version > $oldTask->version) $this->recordTaskVersion($task);
- /* Compute task's story stage. */
- $this->loadModel('story')->setStage($task->story);
- if($task->story != $oldTask->story) $this->story->setStage($oldTask->story);
- /* Create score. */
- if($task->status == 'done') $this->loadModel('score')->create('task', 'finish', $task->id);
- if($task->status == 'closed') $this->loadModel('score')->create('task', 'close', $task->id);
- if($task->status != $oldTask->status)
- {
- $executionInfo = !empty($newExecution) ? $newExecution : $this->loadModel('execution')->fetchByID((int)$oldTask->execution);
- if($executionInfo->type == 'kanban') $this->loadModel('kanban')->updateLane($task->execution, 'task', $task->id);
- }
- $isParentChanged = $task->parent != $oldTask->parent;
- /* If there is a parent task before updating the task, update the parent. */
- $this->updateParent($task, $isParentChanged);
- if($isParentChanged && $oldTask->parent > 0)
- {
- $oldParentTask = $this->dao->select('*')->from(TABLE_TASK)->where('id')->eq($oldTask->parent)->fetch();
- $this->updateParentStatus($task->id, $oldTask->parent, !$isParentChanged);
- $this->computeBeginAndEnd($oldTask->parent);
- $oldChildCount = $this->dao->select('COUNT(1) AS count')->from(TABLE_TASK)->where('parent')->eq($oldTask->parent)->fetch('count');
- if(!$oldChildCount) $this->dao->update(TABLE_TASK)->set('isParent')->eq(0)->where('id')->eq($oldTask->parent)->exec();
- $this->dao->update(TABLE_TASK)->set('lastEditedBy')->eq($this->app->user->account)->set('lastEditedDate')->eq(helper::now())->where('id')->eq($oldTask->parent)->exec();
- $newParentTask = $this->dao->select('*')->from(TABLE_TASK)->where('id')->eq($oldTask->parent)->fetch();
- $changes = common::createChanges($oldParentTask, $newParentTask);
- $this->loadModel('action')->create('task', $task->id, 'unlinkParentTask', '', $oldTask->parent, '', false);
- $actionID = $this->action->create('task', $oldTask->parent, 'unLinkChildrenTask', '', $task->id, '', false);
- if(!empty($changes)) $this->action->logHistory($actionID, $changes);
- }
- if($this->config->edition != 'open' && $oldTask->feedback) $this->loadModel('feedback')->updateStatus('task', $oldTask->feedback, $task->status, $oldTask->status, $oldTask->id);
- if(!empty($oldTask->mode) && empty($task->mode)) $this->dao->delete()->from(TABLE_TASKTEAM)->where('task')->eq($task->id)->exec();
- if(!empty($task->story) && $this->post->syncChildren) $this->syncStoryToChildren($task);
- if(!empty($task->mode) && $task->story != $oldTask->story && $task->storyVersion > $oldTask->storyVersion) $this->dao->update(TABLE_TASKTEAM)->set('storyVersion')->eq($task->storyVersion)->where('task')->eq($task->id)->exec();
- }
- /**
- * 在任务信息中追加泳道名称。
- * Append the lane name to the task information.
- *
- * @param array $tasks
- * @access public
- * @return object[]
- */
- public function appendLane($tasks)
- {
- $executionIdList = array();
- foreach($tasks as $task)
- {
- $task->lane = '';
- if(!isset($executionIdList[$task->execution])) $executionIdList[$task->execution] = $task->execution;
- }
- $lanes = $this->dao->select('t1.kanban,t1.lane,t2.name,t1.cards')->from(TABLE_KANBANCELL)->alias('t1')
- ->leftJoin(TABLE_KANBANLANE)->alias('t2')->on('t1.lane = t2.id')
- ->where('t1.kanban')->in($executionIdList)
- ->andWhere('t2.deleted')->eq(0)
- ->andWhere('t1.type')->eq('task')
- ->andWhere("t1.cards")->ne('')
- ->fetchAll();
- if(empty($lanes)) return $tasks;
- foreach($tasks as $task)
- {
- foreach($lanes as $lane)
- {
- if($lane->kanban != $task->execution) continue;
- if(strpos(",{$lane->cards},", ",{$task->id},") === false) continue;
- $task->lane = $lane->name;
- break;
- }
- }
- return $tasks;
- }
- /**
- * 任务指派用户。
- * Assign a task to a user again.
- *
- * @param object $task
- * @param int $taskID
- * @access public
- * @return array|false
- */
- public function assign($task)
- {
- $oldTask = $this->getById($task->id);
- /* Check task left. */
- if($oldTask->isParent == '0' && !in_array($oldTask->status, array('done', 'closed')) && isset($task->left) && $task->left == 0)
- {
- dao::$errors['left'] = sprintf($this->lang->error->notempty, $this->lang->task->left);
- return false;
- }
- if(!empty($task->left) && $task->left < 0)
- {
- dao::$errors['left'] = sprintf($this->lang->task->error->recordMinus, $this->lang->task->left);
- return false;
- }
- /* Update parent task status. */
- if($oldTask->parent > 0) $this->updateParentStatus($task->id);
- $this->dao->update(TABLE_TASK)
- ->data($task)
- ->autoCheck()
- ->checkFlow()
- ->where('id')->eq($task->id)
- ->exec();
- if(dao::isError()) return false;
- $changes = common::createChanges($oldTask, $task);
- /* Record log. */
- $actionID = $this->loadModel('action')->create('task', $task->id, 'Assigned', $this->post->comment, $task->assignedTo);
- if($actionID) $this->action->logHistory($actionID, $changes);
- return $changes;
- }
- /**
- * 批量创建任务。
- * Batch create tasks.
- *
- * @param array $tasks
- * @param array $output
- * @access public
- * @return array|false
- */
- public function batchCreate($tasks, $output)
- {
- $this->loadModel('story');
- $requireEstimate = strpos($this->config->task->create->requiredFields, 'estimate') !== false;
- $executionID = !empty($tasks) ? current($tasks)->execution : 0;
- $taskIdList = array();
- $parents = array();
- foreach($tasks as $rowIndex => $task)
- {
- /* Get the lane and column of the current task. */
- $laneID = $task->lane;
- $columnID = $task->column;
- $level = $task->level;
- unset($task->lane, $task->column, $task->level);
- if($requireEstimate)
- {
- $requiredFields = ',' . $this->config->task->create->requiredFields . ',';
- if($this->post->isParent[$rowIndex] == '1')
- {
- $requiredFields = str_replace(',estimate,', ',', $requiredFields);
- }
- elseif(strpos($requiredFields, ',estimate,') === false)
- {
- $requiredFields .= 'estimate';
- }
- $this->config->task->create->requiredFields = trim(',', $requiredFields);
- }
- /* Create a task. */
- $taskID = $this->create($task);
- if(!$taskID) return false;
- $parents[$level] = $taskID;
- if($level > 0)
- {
- $task->id = $taskID;
- $task->parent = $parents[$level - 1];
- $this->dao->update(TABLE_TASK)->set('parent')->eq($task->parent)->where('id')->eq($taskID)->exec();
- $this->updateParent($task, false);
- }
- /* Update Kanban and story stage. */
- if(!empty($task->story))
- {
- $this->story->setStage($task->story);
- if($this->config->edition != 'open')
- {
- $relation = new stdClass();
- $relation->relation = 'generated';
- $relation->AID = $task->story;
- $relation->AType = 'story';
- $relation->BID = $taskID;
- $relation->BType = 'task';
- $relation->product = 0;
- $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
- }
- }
- $this->updateKanbanForBatchCreate($taskID, $executionID, $laneID, (int)$columnID);
- $this->executeHooks($taskID);
- $taskIdList[$taskID] = $taskID;
- }
- return $taskIdList;
- }
- /**
- * 批量更改任务所属模块。
- * Batch change the module of task.
- *
- * @param array $taskIdList
- * @param int $moduleID
- * @access public
- * @return bool
- */
- public function batchChangeModule($taskIdList, $moduleID)
- {
- $now = helper::now();
- $oldTasks = $this->getByIdList($taskIdList);
- $this->loadModel('action');
- foreach($taskIdList as $taskID)
- {
- $oldTask = zget($oldTasks, $taskID, null);
- if(!$oldTask || $moduleID == $oldTask->module) continue;
- $task = new stdclass();
- $task->lastEditedBy = $this->app->user->account;
- $task->lastEditedDate = $now;
- $task->module = $moduleID;
- $this->dao->update(TABLE_TASK)->data($task)
- ->autoCheck()
- ->check('module', 'ge', 0)
- ->where('id')->eq((int)$taskID)
- ->exec();
- if(dao::isError()) return false;
- $changes = common::createChanges($oldTask, $task);
- $actionID = $this->action->create('task', (int)$taskID, 'Edited');
- if($actionID) $this->action->logHistory($actionID, $changes);
- }
- return true;
- }
- /**
- * 批量更新任务。
- * Batch update tasks.
- *
- * @param array $taskData
- * @access public
- * @return array[]|false
- */
- public function batchUpdate($taskData)
- {
- $this->loadModel('action');
- $this->loadModel('score');
- if($this->config->edition != 'open')
- {
- $fieldList = $this->loadModel('workflowfield')->getList('task');
- foreach($fieldList as $field)
- {
- if($field->type == 'date' || $field->type == 'datetime') $this->config->task->dateFields[] = $field->field;
- }
- }
- $allChanges = array();
- $oldTasks = $taskData ? $this->getByIdList(array_keys($taskData)) : array();
- foreach($taskData as $taskID => $task)
- {
- foreach($this->config->task->dateFields as $field)
- {
- if(in_array($field, explode(',', $this->config->task->batchedit->requiredFields))) continue;
- if(isset($task->$field) && helper::isZeroDate($task->$field)) $task->$field = null;
- }
- /* Format hours. */
- if(isset($task->estimate)) $task->estimate = round((float)$task->estimate, 2);
- if(isset($task->consumed)) $task->consumed = round((float)$task->consumed, 2);
- if(isset($task->left)) $task->left = round((float)$task->left, 2);
- /* Update a task.*/
- $this->dao->update(TABLE_TASK)->data($task)
- ->autoCheck()
- ->batchCheck($this->config->task->batchedit->requiredFields, 'notempty')
- ->checkFlow()
- ->where('id')->eq($taskID)
- ->exec();
- if(dao::isError())
- {
- foreach(dao::getError() as $field => $error) dao::$errors["{$field}[{$taskID}]"] = $error;
- return false;
- }
- $this->executeHooks($taskID);
- /* Create the task description of the current version in the database. */
- $oldTask = zget($oldTasks, $taskID);
- if($task->version > $oldTask->version)
- {
- $taskSpec = new stdclass();
- $taskSpec->task = $taskID;
- $taskSpec->version = $task->version;
- $taskSpec->name = $task->name;
- $taskSpec->estStarted = isset($task->estStarted) ? $task->estStarted : null;
- $taskSpec->deadline = isset($task->deadline) ? $task->deadline : null;
- $this->dao->insert(TABLE_TASKSPEC)->data($taskSpec)->autoCheck()->exec();
- }
- if(!empty($oldTask->mode) && $task->story != $oldTask->story)
- {
- $storyVersion = $this->dao->select('version')->from(TABLE_STORY)->where('id')->eq($task->story)->limit(1)->fetch('version');
- $this->dao->update(TABLE_TASKTEAM)->set('storyVersion')->eq($storyVersion)->where('task')->eq($task->id)->exec();
- }
- if($task->status == 'done') $this->score->create('task', 'finish', $taskID);
- if($task->status == 'closed') $this->score->create('task', 'close', $taskID);
- $changes = common::createChanges($oldTask, $task);
- if(!empty($changes))
- {
- $extra = '';
- array_map(function($field) use (&$extra) { if($field['field'] == 'status') $extra = 'statuschanged'; }, $changes);
- $actionID = $this->action->create('task', $taskID, 'Edited', '', $extra);
- if($actionID) $this->action->logHistory($actionID, $changes);
- $allChanges[$taskID] = $changes;
- }
- }
- $this->score->create('ajax', 'batchEdit');
- return $allChanges;
- }
- /**
- * 取消一个任务。
- * Cancel a task.
- *
- * @param object $oldTask
- * @param object $task
- * @param array $output
- * @access public
- * @return bool
- */
- public function cancel($oldTask, $task, $output = array())
- {
- $this->dao->update(TABLE_TASK)->data($task)->autoCheck()->checkFlow()->where('id')->eq($oldTask->id)->exec();
- if(dao::isError()) return false;
- if(!empty($oldTask->mode)) $this->dao->update(TABLE_TASKTEAM)->set('status')->eq($task->status)->where('task')->eq($oldTask->id)->exec();
- if(!empty($oldTask->fromBug)) $this->dao->update(TABLE_BUG)->set('toTask')->eq(0)->where('id')->eq($oldTask->fromBug)->exec();
- /* Cancel a parent task. */
- $changes = common::createChanges($oldTask, $task);
- $this->afterChangeStatus($oldTask, $changes, 'Canceled', $output);
- return true;
- }
- /**
- * 检查当前登录用户是否可以操作日志。
- * Check if the current user can operate effort.
- *
- * @param object $task
- * @param object $effort
- * @access public
- * @return bool
- */
- public function canOperateEffort($task, $effort = null)
- {
- if(empty($task->team))
- {
- if($effort === null) $effort = new stdclass();
- return $this->loadModel('common')->canOperateEffort($effort);
- }
- /* Check for add effort. */
- if(empty($effort))
- {
- $members = array_column($task->team, 'account');
- if(!in_array($this->app->user->account, $members)) return false;
- if($task->mode == 'linear' && $this->app->user->account != $task->assignedTo) return false;
- return true;
- }
- /* Check for edit and delete effort. */
- if($task->mode == 'linear')
- {
- if(in_array($task->status, array('pause', 'cancel', 'closed'))) return false;
- if($task->status == 'doing') return $effort->account == $this->app->user->account;
- }
- return $this->loadModel('common')->canOperateEffort($effort);
- }
- /**
- * 检查开始日期和截止日期。
- * Check estStarted and deadline date.
- *
- * @param int $executionID
- * @param string $estStarted
- * @param string $deadline
- * @param int|null $rowID
- * @access public
- * @return false|void
- */
- public function checkEstStartedAndDeadline($executionID, $estStarted, $deadline, $rowID = null)
- {
- $beginIndex = $rowID === null ? 'estStarted' : "estStarted[$rowID]";
- $endIndex = $rowID === null ? 'deadline' : "deadline[$rowID]";
- $execution = $this->loadModel('execution')->getByID($executionID);
- if(empty($execution) || empty($this->config->limitTaskDate)) return false;
- if(empty($execution->multiple)) $this->lang->execution->common = $this->lang->project->common;
- if(!empty($estStarted) && !helper::isZeroDate($estStarted))
- {
- if($estStarted < $execution->begin) dao::$errors[$beginIndex] = sprintf($this->lang->task->error->beginLtExecution, $this->lang->execution->common, $execution->begin);
- if($estStarted > $execution->end) dao::$errors[$beginIndex] = sprintf($this->lang->task->error->beginGtExecution, $this->lang->execution->common, $execution->end);
- }
- if(!empty($deadline) && !helper::isZeroDate($deadline))
- {
- if($deadline > $execution->end) dao::$errors[$endIndex] = sprintf($this->lang->task->error->endGtExecution, $this->lang->execution->common, $execution->end);
- if($deadline < $execution->begin) dao::$errors[$endIndex] = sprintf($this->lang->task->error->endLtExecution, $this->lang->execution->common, $execution->begin);
- }
- }
- /**
- * 关闭任务。
- * Close a task.
- *
- * @param object $oldTask
- * @param object $task
- * @param mixed[] $output
- * @access public
- * @return bool|array
- */
- public function close($oldTask, $task, $output = array())
- {
- $this->dao->update(TABLE_TASK)->data($task)->autoCheck()->checkFlow()->where('id')->eq((int)$oldTask->id)->exec();
- if(dao::isError()) return false;
- if(!empty($oldTask->mode)) $this->dao->update(TABLE_TASKTEAM)->set('`status`')->eq($task->status)->where('task')->eq($task->id)->exec();
- $changes = common::createChanges($oldTask, $task);
- $this->afterChangeStatus($oldTask, $changes, 'Closed', $output);
- $this->loadModel('score')->create('task', 'close', $task->id);
- /* Confirm need update issue status. */
- if(isset($oldTask->fromIssue) && $oldTask->fromIssue > 0)
- {
- $fromIssue = $this->loadModel('issue')->getByID($oldTask->fromIssue);
- if($fromIssue->status != 'closed')
- {
- $confirmURL = $this->createLink('issue', 'close', "id=$oldTask->fromIssue");
- $cancelURL = $this->createLink('task', 'view', "taskID=$oldTask->id");
- return array('result' => 'success', 'load' => array('confirm' => sprintf($this->lang->task->remindIssue, $oldTask->fromIssue), 'confirmed' => $confirmURL, 'canceled' => $cancelURL));
- }
- }
- return true;
- }
- /**
- * 根据父任务ID计算父任务的预计开始 实际开始 截止日期。
- * Compute begin and end for parent task.
- *
- * @param int $taskID
- * @access public
- * @return bool
- */
- public function computeBeginAndEnd($taskID)
- {
- /* Get estStarted realStarted and deadline of the sub-tasks. */
- $tasks = $this->dao->select('estStarted, realStarted, deadline')->from(TABLE_TASK)->where('parent')->eq($taskID)->andWhere('status')->ne('cancel')->andWhere('deleted')->eq(0)->fetchAll();
- if(empty($tasks)) return !dao::isError();
- /* Initialize task data and update it. */
- $parent = $this->fetchById($taskID);
- $taskDateLimit = $this->dao->select('taskDateLimit')->from(TABLE_PROJECT)->where('id')->eq($parent->project)->fetch('taskDateLimit');
- if($taskDateLimit == 'limit') return !dao::isError();
- /* Compute the earliest estStarted, the earliest realStarted and the latest deadline. */
- $earliestEstStarted = '';
- $earliestRealStarted = '';
- $latestDeadline = '';
- foreach($tasks as $task)
- {
- if(!helper::isZeroDate($task->estStarted) && (empty($earliestEstStarted) || $earliestEstStarted > $task->estStarted)) $earliestEstStarted = $task->estStarted;
- if(!helper::isZeroDate($task->realStarted) && (empty($earliestRealStarted) || $earliestRealStarted > $task->realStarted)) $earliestRealStarted = $task->realStarted;
- if(!helper::isZeroDate($task->deadline) && (empty($latestDeadline) || $latestDeadline < $task->deadline)) $latestDeadline = $task->deadline;
- }
- $newTask = array();
- if(!empty($earliestEstStarted) && !helper::isZeroDate($parent->estStarted) && $parent->estStarted > $earliestEstStarted) $newTask['estStarted'] = $earliestEstStarted;
- if(!empty($earliestRealStarted) && !helper::isZeroDate($parent->realStarted) && $parent->realStarted > $earliestRealStarted) $newTask['realStarted'] = $earliestRealStarted;
- if(!empty($latestDeadline) && !helper::isZeroDate($parent->deadline) && $parent->deadline < $latestDeadline) $newTask['deadline'] = $latestDeadline;
- if(!empty($newTask)) $this->dao->update(TABLE_TASK)->data($newTask)->autoCheck()->where('id')->eq($taskID)->exec();
- if($parent->parent) $this->computeBeginAndEnd($parent->parent);
- return !dao::isError();
- }
- /**
- * 计算多人任务工时。
- * Compute hours for multiple task.
- *
- * @param object $oldTask
- * @param object $task
- * @param array $team
- * @param bool $autoStatus
- * @access public
- * @return object|bool
- */
- public function computeMultipleHours($oldTask, $task = null, $team = array(), $autoStatus = true)
- {
- if(!$oldTask) return false;
- if(empty($team)) $team = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->eq($oldTask->id)->orderBy('order')->fetchAll('id', false); // If the team is empty, get the team from the task team table.
- /* If the team is not empty, compute the team hours. */
- if(!empty($team))
- {
- /* Get members, old team and current task. */
- $members = array_column($team, 'account');
- $oldTeam = zget($oldTask, 'team', array());
- $currentTask = !empty($task) ? clone $task : new stdclass();
- if(!isset($currentTask->status)) $currentTask->status = $oldTask->status;
- $oldTask->team = $team;
- if(isset($task->mode)) $oldTask->mode = $task->mode;
- /* If the assignedTo is not empty, the current task assignedTo is assignedTo. */
- if(!empty($_POST['assignedTo']) && is_string($_POST['assignedTo']))
- {
- $currentTask->assignedTo = $this->post->assignedTo;
- if(!in_array($currentTask->assignedTo, $members)) $currentTask->assignedTo = $this->getAssignedTo4Multi($members, $oldTask);
- }
- /* If assignedTo is empty, get the assignedTo for the multiply linear task. */
- else
- {
- $currentTask->assignedTo = $this->getAssignedTo4Multi($members, $oldTask);
- }
- if(isset($task->mode) && $task->mode == 'multi' && strpos(',wait,doing,pause,', ",{$task->status},") !== false) $currentTask->assignedTo = '';
- if($oldTask->assignedTo != $currentTask->assignedTo) $currentTask->assignedDate = helper::now();
- $oldTask->team = $oldTeam;
- /* Compute estimate and left. */
- $currentTask->estimate = 0;
- $currentTask->left = 0;
- foreach($team as $member)
- {
- $currentTask->estimate += (float)$member->estimate;
- $currentTask->left += (float)$member->left;
- }
- /* Get task efforts, and compute consumed. */
- $efforts = $this->getTaskEfforts($oldTask->id);
- $currentTask->consumed = 0;
- foreach($efforts as $effort) $currentTask->consumed += (float)$effort->consumed;
- /* If task is not empty, the task status is computed and the task is returned. */
- if(!empty($task)) return $this->taskTao->computeTaskStatus($currentTask, $oldTask, $task, $autoStatus, !empty($efforts), $members);
- /* If task is empty, update the current task. */
- $currentTask->consumed = round((float)$currentTask->consumed, 2);
- $currentTask->left = round((float)$currentTask->left, 2);
- $this->dao->update(TABLE_TASK)->data($currentTask)->autoCheck()->where('id')->eq($oldTask->id)->exec();
- }
- return !dao::isError();
- }
- /**
- * 根据父任务ID计算父任务的预计、消耗和剩余工时。
- * Compute parent task working hours.
- *
- * @param int|bool $taskID
- * @access public
- * @return bool
- */
- public function computeWorkingHours($taskID)
- {
- if(!$taskID) return true;
- /* Get sub-tasks. */
- $tasks = $this->dao->select('`id`,`estimate`,`consumed`,`left`, status')->from(TABLE_TASK)->where('parent')->eq($taskID)->andWhere('status')->ne('cancel')->andWhere('deleted')->eq(0)->fetchAll('id');
- /* If task doesn't have sub-tasks, clear out the consumed hours. */
- if(empty($tasks))
- {
- $this->dao->update(TABLE_TASK)->set('consumed')->eq(0)->where('id')->eq($taskID)->exec();
- return !dao::isError();
- }
- /* Compute task estimate, consumed and left through sub-tasks. */
- $estimate = 0;
- $consumed = 0;
- $left = 0;
- foreach($tasks as $task)
- {
- $estimate += $task->estimate;
- $consumed += $task->consumed;
- if($task->status != 'closed') $left += $task->left;
- }
- /* Initialize task data to update. */
- $newTask = new stdclass();
- $newTask->estimate = $estimate;
- $newTask->consumed = $consumed;
- $newTask->left = $left;
- $newTask->lastEditedBy = $this->app->user->account;
- $newTask->lastEditedDate = helper::now();
- /* Update task data. */
- $this->dao->update(TABLE_TASK)->data($newTask)->autoCheck()->where('id')->eq($taskID)->exec();
- return !dao::isError();
- }
- /**
- * 创建一个任务。
- * Create a task.
- *
- * @param object $task
- * @param bool $createAction
- * @access public
- * @return false|int
- */
- public function create($task, $createAction = true)
- {
- /* If the lifetime if the execution is ops and the attribute of execution is request or review, remove story from required fields. */
- $execution = $this->dao->findByID($task->execution)->from(TABLE_PROJECT)->fetch();
- $requiredFields = ',' . $this->config->task->create->requiredFields . ',';
- if($execution && $this->isNoStoryExecution($execution)) $requiredFields = str_replace(',story,', ',', $requiredFields);
- /* Insert task data. */
- if(!empty($execution->isTpl)) $task->isTpl = $execution->isTpl;
- if(empty($task->assignedTo)) unset($task->assignedDate);
- if(isset($task->estimate)) $task->estimate = round((float)$task->estimate, 2);
- if(isset($task->left)) $task->left = round((float)$task->left, 2);
- $this->dao->insert(TABLE_TASK)->data($task, 'docVersions')
- ->checkIF($task->estimate != '', 'estimate', 'float')
- ->autoCheck()
- ->batchCheck($requiredFields, 'notempty')
- ->checkFlow()
- ->exec();
- if(dao::isError()) return false;
- /* Get task id. */
- $taskID = $this->dao->lastInsertID();
- if(!empty($task->parent))
- {
- $task->id = $taskID;
- $this->updateParent($task, false);
- unset($task->id);
- }
- else
- {
- $this->dao->update(TABLE_TASK)->set('path')->eq(",$taskID,")->where('id')->eq($taskID)->exec();
- }
- /* Insert task desc data. */
- $taskSpec = new stdclass();
- $taskSpec->task = $taskID;
- $taskSpec->version = $task->version;
- $taskSpec->name = $task->name;
- if(!empty($task->estStarted)) $taskSpec->estStarted = $task->estStarted;
- if(!empty($task->deadline)) $taskSpec->deadline = $task->deadline;
- $this->dao->insert(TABLE_TASKSPEC)->data($taskSpec)->autoCheck()->exec();
- if(dao::isError()) return false;
- $this->dao->commit();
- if($createAction)
- {
- $this->loadModel('action')->create('task', $taskID, 'Opened', '');
- if(!empty($task->assignedTo)) $this->action->create('task', $taskID, 'Assigned', '', $task->assignedTo);
- }
- $this->loadModel('file')->updateObjectID($this->post->uid, $taskID, 'task');
- $this->loadModel('score')->create('task', 'create', $taskID);
- if(dao::isError()) return false;
- return $taskID;
- }
- /**
- * 从GitLab议题创建任务。
- * Create task from gitlab issue.
- *
- * @param object $task
- * @param int $executionID
- * @access public
- * @return int
- */
- public function createTaskFromGitlabIssue($task, $executionID)
- {
- $task->version = 1;
- $task->openedBy = $this->app->user->account;
- $task->lastEditedBy = $this->app->user->account;
- $task->assignedDate = isset($task->assignedTo) ? helper::now() : null;
- $task->story = 0;
- $task->module = 0;
- $task->estimate = 0;
- $task->deadline = $task->deadline ?? null;
- $task->estStarted = null;
- $task->left = 0;
- $task->pri = 3;
- $task->type = 'devel';
- $task->execution = $task->execution ?? $executionID;
- $task->project = $this->dao->select('project')->from(TABLE_PROJECT)->where('id')->eq($executionID)->fetch('project');
- /* Set project of the task to 0 if the project of execution is not exist. */
- if(empty($task->project)) $task->project = 0;
- $this->dao->insert(TABLE_TASK)->data($task, 'id,product')
- ->autoCheck()
- ->batchCheck($this->config->task->create->requiredFields, 'notempty')
- ->checkIF(!helper::isZeroDate($task->deadline), 'deadline', 'ge', $task->estStarted)
- ->exec();
- if(dao::isError()) return false;
- return $this->dao->lastInsertID();
- }
- /**
- * 创建事务类型的任务。
- * Create a task of affair type.
- *
- * @param object $task
- * @param array $assignedToList
- * @access public
- * @return false|array
- */
- public function createTaskOfAffair($task, $assignedToList)
- {
- $taskIdList = array();
- foreach($assignedToList as $assignedTo)
- {
- /* If the type of task is affair and assignedTo is empty, skip it. */
- if(count($assignedToList) > 1 && empty($assignedTo)) continue;
- $task->assignedTo = $assignedTo;
- $taskID = $this->create($task);
- if(!$taskID) return false;
- $taskIdList[] = $taskID;
- }
- if(dao::isError()) return false;
- return $taskIdList;
- }
- /**
- * 创建测试类型的任务。
- * Create a test type task.
- *
- * @param object $task
- * @param array $testTasks
- * @access public
- * @return false|int
- */
- public function createTaskOfTest($task, $testTasks)
- {
- $this->config->task->create->requiredFields = str_replace(array(',estimate,', ',story,', ',estStarted,', ',deadline,', ',module,'), ',', ",{$this->config->task->create->requiredFields},");
- $taskID = $this->create($task);
- if(!$taskID) return false;
- /* If the current task has test subtasks, create test subtasks and update the task information. */
- if(!empty($testTasks))
- {
- $this->createTestChildTasks($taskID, $testTasks);
- $this->computeWorkingHours($taskID);
- $this->computeBeginAndEnd($taskID);
- $this->dao->update(TABLE_TASK)->set('isParent')->eq(1)->where('id')->eq($taskID)->exec();
- }
- if(dao::isError()) return false;
- return $taskID;
- }
- /**
- * 创建关联需求的测试类型的子任务。
- * Create a subtask for the test type story with the story.
- *
- * @param int $taskID
- * @param object[] $testTasks
- * @access public
- * @return void
- */
- public function createTestChildTasks($taskID, $testTasks)
- {
- $this->loadModel('action');
- /* Get the stories of the test tasks. */
- $testStoryIdList = array();
- foreach($testTasks as $task)
- {
- if(!isset($task->story)) continue;
- $testStoryIdList[$task->story] = $task->story;
- }
- $testStories = $this->dao->select('id,title,version,module')->from(TABLE_STORY)->where('id')->in($testStoryIdList)->fetchAll('id');
- $parentTask = $this->fetchById($taskID);
- foreach($testTasks as $task)
- {
- /* If the story id is not exist, skip it. */
- $storyID = isset($task->story) ? $task->story : 0;
- if(!isset($testStories[$storyID])) continue;
- /* Construct a task and create it. */
- $task->parent = $taskID;
- $task->storyVersion = $testStories[$storyID]->version;
- $task->name = $this->lang->task->lblTestStory . " #{$storyID} " . $testStories[$storyID]->title;
- $task->module = $testStories[$storyID]->module;
- $this->dao->insert(TABLE_TASK)->data($task)->exec();
- /* Get task id and create a action. */
- $childTaskID = $this->dao->lastInsertID();
- $this->action->create('task', $childTaskID, 'Opened');
- if(!empty($parentTask)) $this->dao->update(TABLE_TASK)->set('path')->eq("{$parentTask->path}{$childTaskID},")->where('id')->eq($childTaskID)->exec();
- if($this->config->edition != 'open')
- {
- $relation = new stdClass();
- $relation->relation = 'generated';
- $relation->AID = $task->story;
- $relation->AType = 'story';
- $relation->BID = $childTaskID;
- $relation->BType = 'task';
- $relation->product = 0;
- $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
- }
- $this->taskTao->updateRelation($childTaskID, (int)$taskID);
- }
- }
- /**
- * 创建多人任务。
- * Create a multiplayer task.
- *
- * @param object $task
- * @param object $teamData
- * @access public
- * @return false|int
- */
- public function createMultiTask($task, $teamData)
- {
- $task->assignedTo = '';
- $taskID = $this->create($task, false);
- if(!$taskID) return false;
- if(count(array_filter($teamData->team)) < 2) return $taskID;
- /* Manage the team of task and calculate the team hours. */
- $task->id = $taskID;
- $teams = $this->manageTaskTeam($task->mode, $task, $teamData);
- if($teams)
- {
- $this->computeMultipleHours($task);
- $this->loadModel('program')->refreshProjectStats($task->project);
- }
- if(!empty($task->story))
- {
- $storyVersion = $this->dao->select('version')->from(TABLE_STORY)->where('id')->eq($task->story)->limit(1)->fetch('version');
- $this->dao->update(TABLE_TASKTEAM)->set('storyVersion')->eq($storyVersion)->where('task')->eq($task->id)->exec();
- }
- /* Send mail after created team. */
- $this->loadModel('action')->create('task', $taskID, 'Opened', '');
- return $taskID;
- }
- /**
- * 删除工时并更新任务。 Delete the work hour from the task.
- *
- * @param int $effortID
- * @access public
- * @return array|false
- */
- public function deleteWorkhour($effortID)
- {
- $effort = $this->getEffortByID($effortID);
- if(empty($effort))
- {
- dao::$errors[] = $this->lang->notFound;
- return false;
- }
- $task = $this->getById($effort->objectID);
- $data = $this->taskTao->getTaskAfterDeleteWorkhour($effort, $task);
- /* 删除工时;如果任务是多人任务,则重新计算工时。*/
- $this->dao->update(TABLE_EFFORT)->set('deleted')->eq('1')->where('id')->eq($effort->id)->exec();
- if(!empty($task->team)) $data = $this->computeMultipleHours($task, $data) ?? $data;
- /* 更新任务、父任务状态、需求阶段。*/
- $this->dao->update(TABLE_TASK)->data($data) ->where('id')->eq($effort->objectID)->exec();
- if($task->consumed != $data->consumed || $task->left != $data->left) $this->loadModel('program')->refreshProjectStats($task->project);
- if($task->parent > 0) $this->updateParentStatus($task->id);
- if($task->story) $this->loadModel('story')->setStage($task->story);
- /* 计算此工时所对应任务的变更。*/
- $oldTask = new stdclass();
- $oldTask->consumed = $task->consumed;
- $oldTask->left = $task->left;
- $oldTask->status = $task->status;
- $newTask = new stdclass();
- $newTask->consumed = $data->consumed;
- $newTask->left = $data->left;
- $newTask->status = $data->status;
- if(!dao::isError()) return common::createChanges($oldTask, $newTask);
- }
- /**
- * 完成任务。
- * Finish a task.
- *
- * @param object $oldTask
- * @param object $task
- * @access public
- * @return bool|array
- */
- public function finish($oldTask, $task)
- {
- $currentTeam = !empty($oldTask->team) ? $this->getTeamByAccount($oldTask->team) : array();
- if($currentTeam)
- {
- $consumed = $currentTeam->consumed + (float)$this->post->currentConsumed;
- $consumed = round((float)$consumed, 2);
- $this->dao->update(TABLE_TASKTEAM)->set('left')->eq(0)->set('consumed')->eq($consumed)->set('status')->eq('done')->where('id')->eq($currentTeam->id)->exec();
- $task = $this->computeMultipleHours($oldTask, $task);
- }
- if(isset($task->consumed)) $task->consumed = round((float)$task->consumed, 2);
- $this->dao->update(TABLE_TASK)->data($task)->autoCheck()->checkFlow()->where('id')->eq((int)$oldTask->id)->exec();
- if(dao::isError()) return false;
- if($task->consumed != $oldTask->consumed || $task->left != $oldTask->left) $this->loadModel('program')->refreshProjectStats($oldTask->project);
- return common::createChanges($oldTask, $task);
- }
- /**
- * 获取多人串行任务的指派人。
- * Get the assignedTo for the multiply linear task.
- *
- * @param string|array $members
- * @param object $task
- * @param string $type current|next
- * @access public
- * @return string
- */
- public function getAssignedTo4Multi($members, $task, $type = 'current')
- {
- if(!$members || empty($task->team) || $task->mode != 'linear') return $task->assignedTo;
- /* Format task team members. */
- if(!is_array($members)) $members = explode(',', trim($members, ','));
- $members = array_values($members);
- if(is_object($members[0])) $members = array_column($members, 'account');
- /* Get the member of the first unfinished task. */
- $teamHours = array_values($task->team);
- foreach($members as $i => $account)
- {
- if(isset($teamHours[$i]) && $teamHours[$i]->status == 'done') continue;
- if($type == 'current') return $account;
- break;
- }
- /* Get the member of the second unfinished task. */
- if($type == 'next' && isset($members[$i + 1])) return $members[$i + 1];
- return $task->openedBy;
- }
- /**
- * 通过任务ID获取任务的信息。
- * Get task info by ID.
- *
- * @param int $taskID
- * @param bool $setImgSize
- * @access public
- * @return false|object
- */
- public function getByID($taskID, $setImgSize = false, $vision = '')
- {
- if(common::isTutorialMode()) return $this->loadModel('tutorial')->getTask();
- if($vision == '') $vision = $this->config->vision; // TODO: $vision is for compatibling with viewing drill data.
- $task = $this->dao->select('t1.*, t2.id AS storyID, t2.title AS storyTitle, t2.version AS latestStoryVersion, t2.status AS storyStatus, t2.deleted AS storyDeleted')
- ->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
- ->where('t1.id')->eq($taskID)
- ->beginIf($vision != 'all')->andWhere('t1.vision')->eq($this->config->vision)->fi()
- ->fetch();
- if(!$task) return false;
- /* Format data. */
- $task->openedDate = !empty($task->openedDate) ? substr($task->openedDate, 0, 19) : null;
- $task->finishedDate = !empty($task->finishedDate) ? substr($task->finishedDate, 0, 19) : null;
- $task->canceledDate = !empty($task->canceledDate) ? substr($task->canceledDate, 0, 19) : null;
- $task->closedDate = !empty($task->closedDate) ? substr($task->closedDate, 0, 19) : null;
- $task->lastEditedDate = !empty($task->lastEditedDate) ? substr($task->lastEditedDate, 0, 19) : null;
- $task->realStarted = !empty($task->realStarted) ? substr($task->realStarted, 0, 19) : null;
- /* Get the child tasks of the parent task. */
- $childIdList = $this->getAllChildId($taskID, false);
- $children = $this->dao->select('t1.*, t2.id AS storyID, t2.title AS storyTitle, t2.version AS latestStoryVersion, t2.status AS storyStatus')
- ->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
- ->where('t1.id')->in($childIdList)
- ->beginIf($vision != 'all')->andWhere('t1.vision')->eq($this->config->vision)->fi()
- ->fetchAll('id', false);
- foreach($children as $child)
- {
- $child->team = array();
- $child->members = array();
- }
- $task->children = $this->processTasks($children);
- if($task->parent > 0) $task->parentName = $this->dao->findById($task->parent)->from(TABLE_TASK)->fetch('name');
- /* Get task team and team members. */
- if(!empty($task->mode))
- {
- $task->members = array();
- $task->team = $this->getTeamByTask($taskID);
- foreach($task->team as $member)
- {
- $task->members[$member->account] = $member->account;
- if($member->account == $this->app->user->account) $task->storyVersion = $member->storyVersion;
- }
- }
- else
- {
- $task->members = array();
- $task->team = false;
- }
- $task = $this->loadModel('file')->replaceImgURL($task, 'desc');
- $task->files = $this->file->getByObject('task', $taskID);
- if($setImgSize && $task->desc) $task->desc = $this->file->setImgSize($task->desc);
- /* Process a task, compute its progress and get its related information. */
- return $this->processTask($task, false);
- }
- /**
- * 通过任务ID列表批量获取任务信息。
- * Get the task information from the task ID list.
- *
- * @param array $taskIdList
- * @access public
- * @return object[]
- */
- public function getByIdList($taskIdList = array())
- {
- if(empty($taskIdList)) return array();
- return $this->dao->select('*')->from(TABLE_TASK)
- ->where('deleted')->eq(0)
- ->andWhere('id')->in($taskIdList)
- ->fetchAll('id', false);
- }
- /**
- * 获取指定的任务 id name 键值对。
- * Get task pairs by task ID list.
- *
- * @param array $taskIdList
- * @access public
- * @return array
- */
- public function getPairsByIdList($taskIdList = array())
- {
- $taskPairs = $this->dao->select('id, name')->from(TABLE_TASK)->where('deleted')->eq('0')->andWhere('id')->in($taskIdList)->fetchPairs();
- return array(0 => '') + $taskPairs;
- }
- /**
- * 获取按每天完成统计的报表数据。
- * Get report data of finished tasks per day.
- *
- * @access public
- * @return object[]
- */
- public function getDataOfFinishedTasksPerDay()
- {
- $tasks = $this->dao->select("id, DATE_FORMAT(`finishedDate`, '%Y-%m-%d') AS `date`")->from(TABLE_TASK)
- ->where($this->reportCondition())
- ->andWhere('finishedDate')->notZeroDatetime()
- ->orderBy('finishedDate asc')
- ->fetchAll('id');
- if(!$tasks) return array();
- return $this->processData4Report($tasks, array(), 'date');
- }
- /**
- * 获取按指派给统计的报表数据。
- * Get report data of tasks per assignedTo.
- *
- * @access public
- * @return object[]
- */
- public function getDataOfTasksPerAssignedTo()
- {
- $tasks = $this->dao->select('id,assignedTo')->from(TABLE_TASK)->alias('t1')
- ->where($this->reportCondition())
- ->fetchAll('id');
- if(!$tasks) return array();
- $dataList = $this->processData4Report($tasks, array(), 'assignedTo');
- /* Get user's realname. */
- if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter');
- foreach($dataList as $account => $data)
- {
- if(isset($this->users[$account])) $data->name = $this->users[$account];
- }
- return $dataList;
- }
- /**
- * 获取按关闭原因统计的报表数据。
- * Get report data of tasks per closed reason.
- *
- * @access public
- * @return object[]
- */
- public function getDataOfTasksPerClosedReason()
- {
- $tasks = $this->dao->select('id,closedReason')->from(TABLE_TASK)->alias('t1')
- ->where($this->reportCondition())
- ->andWhere('closedReason')->ne('')
- ->fetchAll('id');
- if(!$tasks) return array();
- $dataList = $this->processData4Report($tasks, array(), 'closedReason');
- foreach($dataList as $closedReason => $data)
- {
- if(isset($this->lang->task->reasonList[$closedReason])) $data->name = $this->lang->task->reasonList[$closedReason];
- }
- return $dataList;
- }
- /**
- * 获取按消耗时间统计的报表数据。
- * Get report data of tasks per consumed.
- *
- * @access public
- * @return object[]
- */
- public function getDataOfTasksPerConsumed()
- {
- $tasks = $this->dao->select('id,consumed')->from(TABLE_TASK)->alias('t1')
- ->where($this->reportCondition())
- ->fetchAll('id');
- if(!$tasks) return array();
- $children = $this->dao->select('id,parent,consumed')->from(TABLE_TASK)->where('parent')->in(array_keys($tasks))->fetchAll('id');
- return $this->processData4Report($tasks, $children, 'consumed');
- }
- /**
- * 获取按照截止日期统计的报表数据。
- * Get report data of tasks per deadline.
- *
- * @access public
- * @return object[]
- */
- public function getDataOfTasksPerDeadline()
- {
- $tasks = $this->dao->select('id,deadline')->from(TABLE_TASK)->alias('t1')
- ->where($this->reportCondition())
- ->orderBy('deadline asc')
- ->fetchAll('id');
- if(!$tasks) return array();
- return $this->processData4Report($tasks, array(), 'deadline');
- }
- /**
- * 获取按预计时间统计的报表数据。
- * Get report data of tasks per estimate.
- *
- * @access public
- * @return object[]
- */
- public function getDataOfTasksPerEstimate()
- {
- $tasks = $this->dao->select('id,estimate')->from(TABLE_TASK)->alias('t1')
- ->where($this->reportCondition())
- ->fetchAll('id');
- if(!$tasks) return array();
- $children = $this->dao->select('id,parent,estimate')->from(TABLE_TASK)->where('parent')->in(array_keys($tasks))->fetchAll('id');
- return $this->processData4Report($tasks, $children, 'estimate');
- }
- /**
- * 获取按执行任务数统计的报表数据。
- * Get report data of tasks per execution.
- *
- * @access public
- * @return object[]
- */
- public function getDataOfTasksPerExecution()
- {
- $tasks = $this->taskTao->getListByReportCondition('execution', $this->reportCondition());
- if(!$tasks) return array();
- $dataList = $this->processData4Report($tasks, array(), 'execution');
- /* Get execution names for these tasks. */
- $executions = $this->loadModel('execution')->fetchPairs(0, 'all', true, true);
- foreach($dataList as $executionID => $data) $data->name = isset($executions[$executionID]) ? $executions[$executionID] : $this->lang->report->undefined;
- return $dataList;
- }
- /**
- * 获取按由谁完成统计的报表数据。
- * Get report data of tasks per finishedBy.
- *
- * @access public
- * @return object[]
- */
- public function getDataOfTasksPerFinishedBy()
- {
- $tasks = $this->dao->select('id,finishedBy')->from(TABLE_TASK)->alias('t1')
- ->where($this->reportCondition())
- ->andWhere('finishedBy')->ne('')
- ->fetchAll('id');
- if(!$tasks) return array();
- $dataList = $this->processData4Report($tasks, array(), 'finishedBy');
- /* Get user's realname. */
- if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter');
- foreach($dataList as $account => $data)
- {
- if(isset($this->users[$account])) $data->name = $this->users[$account];
- }
- return $dataList;
- }
- /**
- * 获取按剩余时间统计的报表数据。
- * Get report data of tasks per left.
- *
- * @access public
- * @return object[]
- */
- public function getDataOfTasksPerLeft()
- {
- $tasks = $this->dao->select('id,`left`')->from(TABLE_TASK)->alias('t1')
- ->where($this->reportCondition())
- ->fetchAll('id');
- if(!$tasks) return array();
- $children = $this->dao->select('id,parent,`left`')->from(TABLE_TASK)->where('parent')->in(array_keys($tasks))->fetchAll('id');
- return $this->processData4Report($tasks, $children, 'left');
- }
- /**
- * 获取按模块任务数统计的报表数据。
- * Get report data of tasks per module.
- *
- * @access public
- * @return object[]
- */
- public function getDataOfTasksPerModule()
- {
- $tasks = $this->dao->select('id,module')->from(TABLE_TASK)->alias('t1')
- ->where($this->reportCondition())
- ->fetchAll('id');
- if(!$tasks) return array();
- $dataList = $this->processData4Report($tasks, array(), 'module');
- /* Get modules name. */
- $modules = $this->loadModel('tree')->getModulesName(array_keys($dataList), true, true);
- foreach($dataList as $moduleID => $data)
- {
- $data->name = isset($modules[$moduleID]) ? $modules[$moduleID] : '/';
- }
- return $dataList;
- }
- /**
- * 获取按照优先级统计的报表数据。
- * Get report data of tasks per priority.
- *
- * @access public
- * @return object[]
- */
- public function getDataOfTasksPerPri()
- {
- $tasks = $this->dao->select('id,pri')->from(TABLE_TASK)->alias('t1')
- ->where($this->reportCondition())
- ->fetchAll('id');
- if(!$tasks) return array();
- $dataList = $this->processData4Report($tasks, array(), 'pri');
- foreach($dataList as $pri) $pri->name = $this->lang->task->priList[$pri->name];
- return $dataList;
- }
- /**
- * 获取按任务类型统计的报表数据。
- * Get report data of tasks per type.
- *
- * @access public
- * @return object[]
- */
- public function getDataOfTasksPerType()
- {
- $tasks = $this->dao->select('id,type')->from(TABLE_TASK)->alias('t1')
- ->where($this->reportCondition())
- ->fetchAll('id');
- if(!$tasks) return array();
- $dataList = $this->processData4Report($tasks, array(), 'type');
- foreach($dataList as $type => $data)
- {
- if(isset($this->lang->task->typeList[$type])) $data->name = $this->lang->task->typeList[$type];
- }
- return $dataList;
- }
- /**
- * 按照任务状态统计的报表数据。
- * Get report data of status.
- *
- * @access public
- * @return object[]
- */
- public function getDataOfTasksPerStatus()
- {
- $tasks = $this->dao->select('id,status')->from(TABLE_TASK)->alias('t1')
- ->where($this->reportCondition())
- ->fetchAll('id');
- if(!$tasks) return array();
- $dataList = $this->processData4Report($tasks, array(), 'status');
- foreach($dataList as $status => $data) $data->name = $this->lang->task->statusList[$status];
- return $dataList;
- }
- /**
- * 根据日志ID获取日志信息和是否最后一次日志。
- * Get effort data and check last by id.
- *
- * @param int $effortID
- * @access public
- * @return false|object
- */
- public function getEffortByID($effortID)
- {
- $effort = $this->dao->select('*')->from(TABLE_EFFORT)
- ->where('id')->eq($effortID)
- ->fetch();
- if(!$effort ) return false;
- /* If the estimate is the last of its task, status of task will be checked. */
- $lastID = $this->dao->select('id')->from(TABLE_EFFORT)
- ->where('objectID')->eq($effort->objectID)
- ->andWhere('objectType')->eq('task')
- ->andWhere('deleted')->eq('0')
- ->orderBy('date_desc,id_desc')->limit(1)->fetch('id');
- $effort->isLast = $lastID == $effort->id;
- return $effort;
- }
- /**
- * 获取执行下的任务列表信息。
- * Get the task list under a execution.
- *
- * @param int|array $executionID
- * @param int $productID
- * @param string|array $type all|assignedbyme|myinvolved|undone|needconfirm|assignedtome|finishedbyme|delayed|review|wait|doing|done|pause|cancel|closed|array('wait','doing','done','pause','cancel','closed')
- * @param array $modules
- * @param string $orderBy
- * @param object $pager
- * @access public
- * @return array
- */
- public function getExecutionTasks($executionID, $productID = 0, $type = 'all', $modules = array(), $orderBy = 'status_asc, id_desc', $pager = null)
- {
- $tasks = $this->taskTao->fetchExecutionTasks($executionID, $productID, $type, $modules, $orderBy, $pager);
- if(empty($tasks)) return array();
- $taskTeam = $this->taskTao->getTeamMembersByIdList(array_keys($tasks));
- foreach($tasks as $task)
- {
- if(isset($taskTeam[$task->id])) $tasks[$task->id]->team = $taskTeam[$task->id];
- }
- if($this->config->vision == 'lite') $tasks = $this->appendLane($tasks);
- $userList = $this->loadModel('user')->getPairs('noletter|noclosed');
- $parentIdList = array();
- foreach($tasks as &$task)
- {
- $task->assignedToRealName = zget($userList, $task->assignedTo);
- if($task->parent <= 0 || isset($tasks[$task->parent]) || isset($parentIdList[$task->parent])) continue;
- $parentIdList[$task->parent] = $task->parent;
- }
- return $this->processTasks($tasks);
- }
- /**
- * 获取任务 id:name 的数组。
- * Get an array of task id:name.
- *
- * @param int $executionID
- * @param string $status
- * @param string $orderBy
- * @access public
- * @return array
- */
- public function getExecutionTaskPairs($executionID, $status = 'all', $orderBy = 'finishedBy, id_desc')
- {
- $tasks = $this->dao->select('t1.id,t1.name,t1.parent,t2.realname AS finishedByRealName')
- ->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_USER)->alias('t2')->on('t1.finishedBy = t2.account')
- ->where('t1.execution')->eq((int)$executionID)
- ->andWhere('t1.deleted')->eq(0)
- ->beginIF($status != 'all')->andWhere('t1.status')->in($status)->fi()
- ->orderBy($orderBy)
- ->fetchAll('id');
- $taskPairs = array();
- foreach($tasks as $taskID => $task)
- {
- $prefix = $task->parent > 0 ? "[{$this->lang->task->childrenAB}] " : '';
- $taskPairs[$taskID] = $prefix . "$task->id:" . (empty($task->finishedByRealName) ? '' : "$task->finishedByRealName:") . "$task->name";;
- }
- return $taskPairs;
- }
- /**
- * 获取项目下的任务列表。
- * Get project task list.
- *
- * @param int $projectID
- * @param bool $isPairs
- * @param int $executionID
- * @access public
- * @return array
- */
- public function getProjectTaskList($projectID, $isPairs = false, $executionID = 0)
- {
- $taskList = $this->dao->select('*')->from(TABLE_TASK)
- ->where('deleted')->eq(0)
- ->beginIF($this->config->vision)->andWhere('vision')->eq($this->config->vision)->fi()
- ->andWhere('project')->eq($projectID)
- ->beginIF(!$this->app->user->admin)->andWhere('execution')->in($this->app->user->view->sprints)->fi()
- ->fetchAll('id');
- $taskPairs = array();
- foreach($taskList as $taskID => $task)
- {
- $prefix = $task->parent > 0 ? "[{$this->lang->task->childrenAB}] " : '';
- $finishedBy = !empty($task->finishedByRealName) ? "{$task->finishedByRealName}:" : '';
- $task->rawName = !empty($task->rawName) ? $task->rawName : $task->name;
- $task->name = $task->parent > 0 ? "{$prefix}{$task->id}:{$finishedBy}{$task->rawName}" : $task->rawName;
- if(!empty($executionID) && $task->execution != $executionID) $task->name = $task->name . " [{$this->lang->task->otherExecution}]";
- if(!empty($taskList[$task->parent]))
- {
- $parent = $taskList[$task->parent];
- $parent->rawName = !empty($parent->rawName) ? $parent->rawName : $parent->name;
- $parent->name = "[{$this->lang->task->parentAB}] " . "{$parent->id}:" . (empty($parent->finishedByRealName) ? '' : "$parent->finishedByRealName:") . $parent->rawName;
- }
- }
- foreach($taskList as $taskID => $task) $taskPairs[$taskID] = $task->name;
- return $isPairs ? $taskPairs : $taskList;
- }
- /**
- * 获取导出的任务数据。
- * Get export task information.
- *
- * @param string $orderBy
- * @access public
- * @return object[]
- */
- public function getExportTasks($orderBy)
- {
- $sort = common::appendOrder($orderBy);
- $tasks = array();
- if($this->session->taskOnlyCondition)
- {
- $tasks = $this->dao->select('*')->from(TABLE_TASK)->alias('t1')->where($this->session->taskQueryCondition)
- ->beginIF($this->post->exportType == 'selected')->andWhere('t1.id')->in($this->cookie->checkedItem)->fi()
- ->orderBy($sort)
- ->fetchAll('id', false);
- foreach($tasks as $key => $task)
- {
- /* Compute task progress. */
- if($task->consumed == 0 && $task->left == 0)
- {
- $task->progress = 0;
- }
- elseif($task->consumed != 0 && $task->left == 0)
- {
- $task->progress = 100;
- }
- else
- {
- $task->progress = round($task->consumed / ($task->consumed + $task->left), 2) * 100;
- }
- $task->progress .= '%';
- }
- }
- elseif($this->session->taskQueryCondition)
- {
- $stmt = $this->dbh->query($this->session->taskQueryCondition . ($this->post->exportType == 'selected' ? " AND t1.id IN({$this->cookie->checkedItem})" : '') . " ORDER BY " . strtr($orderBy, '_', ' '));
- while($row = $stmt->fetch()) $tasks[$row->id] = $row;
- }
- return $this->processExportTasks($tasks);
- }
- /**
- * 通过给定条件获取任务列表信息。
- * Get task list by condition.
- *
- * @param object $condition
- * @param string $orderBy
- * @param object|null $pager
- * @access public
- * @return object[]
- */
- public function getListByCondition($condition, $orderBy = 'id_desc', $pager = null)
- {
- $defaultValueList = array('priList' => array(), 'assignedToList' => array(), 'statusList' => array(), 'idList' => array(), 'taskName' => '');
- foreach($defaultValueList as $key => $defaultValue)
- {
- if(!isset($condition->$key))
- {
- $condition->$key = $defaultValue;
- continue;
- }
- if(strripos($key, 'list') === strlen($key) - 4 && !is_array($condition->$key)) $condition->$key = array_filter(explode(',', $condition->$key));
- }
- return $this->dao->select('*')->from(TABLE_TASK)
- ->where('deleted')->eq(0)
- ->beginIF(!empty($condition->priList))->andWhere('pri')->in(zget($condition, 'priList', array()))->fi()
- ->beginIF(!empty($condition->assignedToList))->andWhere('assignedTo')->in(zget($condition, 'assignedToList', array()))->fi()
- ->beginIF(!empty($condition->statusList))->andWhere('status')->in(zget($condition, 'statusList', array()))->fi()
- ->beginIF(!empty($condition->idList))->andWhere('id')->in(zget($condition, 'idList', array()))->fi()
- ->beginIF(!empty($condition->taskName))->andWhere('name')->like("%{$condition->taskName}%")->fi()
- ->beginIF(!empty($condition->executionList))->andWhere('execution')->in(zget($condition, 'executionList', array()))->fi()
- ->beginIF(!$this->app->user->admin)->andWhere('execution')->in($this->app->user->view->sprints)->fi()
- ->orderBy($orderBy)
- ->page($pager)
- ->fetchAll('id', false);
- }
- /**
- * 通过需求列表获取对应的任务列表,任务只取部分属性。
- * Get the list of task information by the list of stories.
- *
- * @param int[] $storyIdList
- * @param int $executionID
- * @param int $projectID
- * @access public
- * @return object[]
- */
- public function getListByStories($storyIdList, $executionID = 0, $projectID = 0)
- {
- return $this->dao->select('id, story, parent, name, assignedTo, pri, status, estimate, consumed, closedReason, `left`')
- ->from(TABLE_TASK)
- ->where('story')->in($storyIdList)
- ->andWhere('deleted')->eq('0')
- ->beginIF($executionID)->andWhere('execution')->eq($executionID)->fi()
- ->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
- ->fetchAll('id');
- }
- /**
- * 通过需求获取对应的任务列表,任务只取部分属性。
- * Get the task list by a story.
- *
- * @param int $storyID
- * @param int $executionID
- * @param int $projectID
- * @access public
- * @return object[]
- */
- public function getListByStory($storyID, $executionID = 0, $projectID = 0)
- {
- $tasks = $this->dao->select('id, parent, name, assignedTo, pri, status, isParent, estimate, consumed, closedReason, `left`')
- ->from(TABLE_TASK)
- ->where('story')->eq($storyID)
- ->andWhere('deleted')->eq('0')
- ->beginIF($executionID)->andWhere('execution')->eq($executionID)->fi()
- ->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
- ->fetchAll('id');
- $parentIdList = array();
- foreach($tasks as $task)
- {
- /* 如果任务不是父任务,或者父任务已经在任务列表中,或者父任务已经在当前列表中,则跳过处理。*/
- if($task->parent <= 0 || isset($parentIdList[$task->parent])) continue;
- $parentIdList[$task->parent] = $task->parent;
- }
- $parentTasks = $this->getByIdList($parentIdList);
- $tasks = $this->taskTao->buildTaskTree($tasks, $parentTasks); /* 将子任务放到父任务里,或者将父任务的名字放到子任务里。*/
- return $this->taskTao->batchComputeProgress($tasks); /* 通过任务的消耗和剩余工时计算任务及其子任务的进度,结果以百分比的数字部分显示。*/
- }
- /**
- * 获取任务的团队成员。
- * Get task's team member pairs.
- *
- * @param object $task
- * @access public
- * @return array
- */
- public function getMemberPairs($task)
- {
- if(!is_array($task->team)) return array();
- $users = $this->loadModel('user')->getTeamMemberPairs($task->execution, 'execution', 'nodeleted');
- $members = array();
- foreach($task->team as $member)
- {
- if(isset($users[$member->account])) $members[$member->account] = $users[$member->account];
- }
- return $members;
- }
- /**
- * 获取并行任务的团队成员。
- * Get parallel task's members.
- *
- * @param int $taskID
- * @access public
- * @return array
- */
- public function getMultiTaskMembers($taskID)
- {
- $taskType = $this->dao->select('mode')->from(TABLE_TASK)->where('id')->eq($taskID)->fetch('mode');
- if($taskType != 'multi') return array();
- $teamMembers = $this->dao->select('account')->from(TABLE_TASKTEAM)->where('task')->eq($taskID)->fetchPairs();
- return empty($teamMembers) ? $teamMembers : array_keys($teamMembers);
- }
- /**
- * 获取父任务 id:name 的数组。
- * Get an array of parent task id:name.
- *
- * @param int $executionID
- * @param string $appendIdList
- * @param int $taskID
- * @access public
- * @return array
- */
- public function getParentTaskPairs($executionID, $appendIdList = '', $taskID = 0)
- {
- /*
- 过滤多人任务。
- 过滤已经记录工时的任务。
- 过滤已取消、已关闭的任务。
- 过滤自己和后代任务。
- */
- $children = $this->getAllChildId($taskID);
- $taskList = $this->dao->select('id, name, isParent, consumed')->from(TABLE_TASK)
- ->where('deleted')->eq(0)
- ->andWhere('status')->notin('cancel,closed')
- ->andWhere('parent')->eq('0')
- ->andWhere('execution')->eq($executionID)
- ->andWhere('mode')->eq('')
- ->beginIF($children)->andWhere('id')->notin($children)->fi()
- ->beginIF($appendIdList)->orWhere('id')->in($appendIdList)->fi()
- ->fetchAll();
- $taskPairs = array();
- foreach($taskList as $task)
- {
- if(!$task->isParent && $task->consumed > 0) continue;
- $taskPairs[$task->id] = $task->name;
- }
- return $taskPairs;
- }
- /**
- * 给任务的下拉列表增加标签。
- * Add label to task dropdown list.
- *
- * @param array $tasks
- * @access public
- * @return array
- */
- public function addTaskLabel($tasks)
- {
- $taskList = $this->getByIdList(array_keys($tasks));
- $options = array();
- foreach($taskList as $task)
- {
- if($task->isParent)
- {
- $options[] = array('text' => array('html' => "<span class='label rounded-xl ring-0 inverse bg-opacity-10 text-inherit mr-1 size-sm'>{$this->lang->task->parentAB}</span> #{$task->id} {$task->name}"), 'hint' => $task->name, 'value' => $task->id, 'keys' => $task->name);
- }
- elseif($task->parent > 0)
- {
- $options[] = array('text' => array('html' => "<span class='label rounded-xl ring-0 inverse bg-opacity-10 text-inherit mr-1 size-sm'>{$this->lang->task->childrenAB}</span> #{$task->id} {$task->name}"), 'hint' => $task->name, 'value' => $task->id, 'keys' => $task->name);
- }
- else
- {
- $options[] = array('text' => "#{$task->id} {$task->name}", 'hint' => $task->name, 'value' => $task->id, 'keys' => $task->name);
- }
- }
- return $options;
- }
- /**
- * 获取任务所有子任务id。
- * Get all child task id.
- *
- * @param int $taskID
- * @param bool $includeSelf
- * @access public
- * @return array
- */
- public function getAllChildId($taskID, $includeSelf = true)
- {
- if(!$taskID) return [];
- $task = $this->fetchByID($taskID);
- if(!$task) return [];
- return $this->dao->select('id')->from(TABLE_TASK)
- ->where('path')->like($task->path . '%') // 去除左侧的模糊查询以利用索引提高性能。Remove the left fuzzy query to use index to improve performance.
- ->andWhere('deleted')->eq(0)
- ->beginIF(!$includeSelf)->andWhere('id')->ne($taskID)->fi()
- ->fetchPairs();
- }
- /**
- * 获取关联需求的任务数。
- * Get the number of tasks linked with the story.
- *
- * @param array $stories
- * @param int $executionID
- * @access public
- * @return array
- */
- public function getStoryTaskCounts($stories, $executionID = 0)
- {
- if(empty($stories)) return array();
- $taskCounts = $this->dao->select('story, COUNT(id) AS tasks')
- ->from(TABLE_TASK)
- ->where('story')->in($stories)
- ->andWhere('deleted')->eq(0)
- ->beginIF($executionID)->andWhere('execution')->eq($executionID)->fi()
- ->groupBy('story')
- ->fetchPairs();
- foreach($stories as $storyID)
- {
- if(!isset($taskCounts[$storyID])) $taskCounts[$storyID] = 0;
- }
- return $taskCounts;
- }
- /**
- * 根据任务、人员和日志ID查询并排序任务的日志列表。
- * Get task efforts by taskID, account or effortID.
- *
- * @param int|array $taskIdList
- * @param string $account
- * @param int $effortID
- * @param string $orderBy
- * @access public
- * @return array
- */
- public function getTaskEfforts($taskIdList, $account = '', $effortID = 0, $orderBy = 'date,id')
- {
- return $this->dao->select('*')->from(TABLE_EFFORT)
- ->where('objectType')->eq('task')
- ->andWhere('objectID')->in($taskIdList)
- ->andWhere('deleted')->eq('0')
- ->beginIF($account)->andWhere('account')->eq($account)->fi()
- ->beginIF($effortID)->orWhere('id')->eq($effortID)->fi()
- ->orderBy($orderBy)
- ->fetchAll('id', false);
- }
- /**
- * 获取用户所在多人任务的团队工序。
- * Get the team process of the multi-task by account.
- *
- * @param array $teams
- * @param string $account
- * @param array $extra
- * @access public
- * @return object|bool
- */
- public function getTeamByAccount($teams, $account = '', $extra = array('filter' => 'done'))
- {
- if(empty($account)) $account = $this->app->user->account;
- $filterStatus = zget($extra, 'filter', '');
- $effortID = zget($extra, 'effortID', '');
- $repeatUsers = array();
- $taskID = 0;
- foreach($teams as $team)
- {
- if(isset($extra['order']) && $team->order == $extra['order'] && $team->account == $account) return $team;
- if(empty($taskID) && $effortID) $taskID = $team->task;
- if(isset($repeatUsers[$team->account])) $repeatUsers[$team->account] = 1;
- if(!isset($repeatUsers[$team->account])) $repeatUsers[$team->account] = 0;
- }
- /*
- * 1. No repeat account or account is not repeat account;
- * 2. Not by effort;
- * Then direct get team by account.
- */
- if(empty($repeatUsers[$account]) || empty($effortID))
- {
- foreach($teams as $team)
- {
- if(empty($effortID) && $filterStatus && $team->status == $filterStatus) continue;
- if($team->account == $account) return $team;
- }
- }
- elseif($effortID)
- {
- $efforts = $this->getTaskEfforts($taskID, '', $effortID);
- $prevTeam = current($teams);
- foreach($efforts as $effort)
- {
- $currentTeam = reset($teams);
- if($effort->id == $effortID)
- {
- if($effort->account == $currentTeam->account) return $currentTeam;
- if($effort->account == $prevTeam->account) return $prevTeam;
- return false;
- }
- if($effort->left == 0 && $currentTeam->account == $effort->account) $prevTeam = array_shift($teams);
- }
- }
- return false;
- }
- /**
- * 获取指派用户和抄送给用户列表。
- * Get toList and ccList.
- *
- * @param object $task
- * @param string $action
- * @access public
- * @return array|false
- */
- public function getToAndCcList($task, $action)
- {
- /* Set assignedTo and mailto. */
- $assignedTo = $task->assignedTo;
- $mailto = empty($task->mailto) ? array() : explode(',', $task->mailto);
- if($task->mode == 'multi')
- {
- $teamList = $this->getMultiTaskMembers($task->id);
- $teamList = implode(',', $teamList);
- $assignedTo = $teamList;
- }
- /* If the assignor is closed, treat the completion person as the assignor. */
- if(strtolower($assignedTo) == 'closed') $assignedTo = empty($task->finishedBy) ? $task->openedBy : $task->finishedBy;
- /* If the child task is paused, closed or canceled, append the mailto from parent task. */
- if($task->parent > 0)
- {
- $appendParent = false;
- if(in_array($action, array('paused', 'closed', 'canceled')))
- {
- $actionExtra = $this->dao->select('id,extra')->from(TABLE_ACTION)->where('objectType')->eq('task')->andWhere('objectID')->eq($task->id)->andWhere('action')->eq($action)->orderBy('id_desc')->limit(1)->fetch('extra');
- if($actionExtra != 'autobyparent') $appendParent = true;
- }
- elseif($action == 'edited')
- {
- $actionExtra = $this->dao->select('id,extra')->from(TABLE_ACTION)->where('objectType')->eq('task')->andWhere('objectID')->eq($task->id)->andWhere('action')->eq($action)->orderBy('id_desc')->limit(1)->fetch('extra');
- if($actionExtra == 'statuschanged' && in_array($task->status, array('pause', 'close', 'cancel'))) $appendParent = true;
- }
- if($appendParent)
- {
- $parentTasks = $this->dao->select('id,assignedTo,openedBy,finishedBy,mailto')->from(TABLE_TASK)->where('id')->in($task->path)->andWhere('id')->ne($task->id)->fetchAll('id');
- foreach($parentTasks as $parentTask)
- {
- $parentAssignedTo = $parentTask->assignedTo;
- if(strtolower($parentAssignedTo) == 'closed') $parentAssignedTo = empty($parentTask->finishedBy) ? $parentTask->openedBy : $parentTask->finishedBy;
- $mailto[] = $parentAssignedTo;
- $mailto = array_merge($mailto, empty($parentTask->mailto) ? array() : explode(',', $parentTask->mailto));
- }
- }
- }
- $mailto = array_unique(array_filter(array_map(function($account) use($assignedTo){ return $account == $assignedTo ? '' : $account;}, $mailto)));
- if(empty($assignedTo) && empty($mailto)) return false;
- /* If the assignor is empty, consider the first one with the cc as the assignor. */
- if(empty($assignedTo)) $assignedTo = array_shift($mailto);
- if(in_array($action, array('paused', 'closed', 'canceled')) && $task->parent > 0)
- {
- $parentTasks = $this->dao->select('id,assignedTo,finishedBy,mailto')->from(TABLE_TASK)->where('id')->in($task->path)->fetchAll('id');
- foreach($parentTasks as $parentTask)
- {
- $mailto[] = (strtolower($parentTask->assignedTo) == 'closed') ? $parentTask->finishedBy : $parentTask->assignedTo;
- $mailto += is_null($parentTask->mailto) ? array() : explode(',', trim($parentTask->mailto, ','));
- }
- }
- $mailto = array_unique(array_filter(array_map(function($account) use($assignedTo){ return $account == $assignedTo ? '' : $account;}, $mailto)));
- return array($assignedTo, implode(',', $mailto));
- }
- /**
- * 获取未完成的任务。
- * Get the unfinish tasks.
- *
- * @param int $executionID
- * @access public
- * @return object[]
- */
- public function getUnfinishTasks($executionID)
- {
- return $this->dao->select('*')->from(TABLE_TASK)
- ->where('execution')->eq($executionID)
- ->andWhere('deleted')->eq(0)
- ->andWhere('status')->in('wait,doing,pause')
- ->fetchAll('id', false);
- }
- /**
- * 获取暂停的项目和执行下,指派给指定用户的任务信息。
- * Get tasks assigned to the user for the suspended project and execution.
- *
- * @param string $account
- * @access public
- * @return object[]
- */
- public function getUserSuspendedTasks($account)
- {
- return $this->dao->select('t1.*')
- ->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_EXECUTION)->alias('t2')->on("t1.execution = t2.id")
- ->leftJoin(TABLE_PROJECT)->alias('t3')->on("t1.project = t3.id")
- ->where('t1.assignedTo')->eq($account)
- ->andWhere('(t2.status')->eq('suspended')
- ->orWhere('t3.status')->eq('suspended')
- ->markRight(1)
- ->andWhere('t1.deleted')->eq(0)
- ->beginIF($this->config->vision)->andWhere('t1.vision')->eq($this->config->vision)->fi()
- ->andWhere('t2.deleted')->eq(0)
- ->andWhere('t3.deleted')->eq(0)
- ->fetchAll('id', false);
- }
- /**
- * 通过任务类型获取用户的任务。
- * Get user tasks by type.
- *
- * @param string $account
- * @param string $type
- * @param int $limit
- * @param object $pager
- * @param string $orderBy
- * @param int $projectID
- * @access public
- * @return object[]
- */
- public function getUserTasks($account, $type = 'assignedTo', $limit = 0, $pager = null, $orderBy = 'id_desc', $projectID = 0)
- {
- if($type != 'myInvolved' && !$this->loadModel('common')->checkField(TABLE_TASK, $type)) return array();
- $tasks = $this->taskTao->fetchUserTasksByType($account, $type, $orderBy, $projectID, $limit, $pager);
- if(empty($tasks)) return $tasks;
- $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'task', false);
- $taskTeam = $this->taskTao->getTeamMembersByIdList(array_keys($tasks));
- foreach($taskTeam as $taskID => $team) $tasks[$taskID]->team = $team;
- return $this->processTasks($tasks);
- }
- /**
- * 获取指派给用户的任务 id:name 数组。
- * Get the task id:name array assigned to the user.
- *
- * @param string $account
- * @param string $status
- * @param array $skipExecutionIDList
- * @param array $appendTaskID
- * @access public
- * @return array
- */
- public function getUserTaskPairs($account, $status = 'all', $skipExecutionIDList = array(), $appendTaskID = array())
- {
- $tasks = $this->dao->select('t1.id, t1.name, t2.name as executionName')
- ->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.execution = t2.id')
- ->where('t1.assignedTo')->eq($account)
- ->andWhere('t1.deleted')->eq(0)
- ->andWhere('t2.deleted')->eq(0)
- ->beginIF($this->config->vision)->andWhere('t1.vision')->eq($this->config->vision)->fi()
- ->beginIF($status != 'all')->andWhere('t1.status')->in($status)->fi()
- ->beginIF(!empty($skipExecutionIDList))->andWhere('t1.execution')->notin($skipExecutionIDList)->fi()
- ->beginIF(!empty($appendTaskID))->orWhere('t1.id')->in($appendTaskID)->fi()
- ->fetchAll('id');
- $taskPairs = array();
- foreach($tasks as $task) $taskPairs[$task->id] = $task->executionName . ' / ' . $task->name;
- return $taskPairs;
- }
- /**
- * 判断任务操作是否可以点击。
- * Judge an action is clickable or not.
- *
- * @param object $task
- * @param string $action
- * @access public
- * @return bool
- */
- public static function isClickable($task, $action)
- {
- $action = strtolower($action);
- /* 任务不可修改的话,则无法进行操作。 */
- if(!common::canModify('task', $task)) return false;
- /* IPD任务当关联的需求池需求撤销/移除移除时,任务需要点击确认。*/
- /* IPD Task when the associated demand is retraec / unlink, the task needs to confirm . */
- if($action == 'confirmdemandretract') return !empty($task->confirmeActionType) && $task->confirmeActionType == 'confirmedretract';
- if($action == 'confirmdemandunlink') return !empty($task->confirmeActionType) && $task->confirmeActionType == 'confirmedunlink';
- /* 如果是转任务,直接返回 true。 */
- if($action == 'totask') return true;
- /* 父任务只能编辑、创建子任务和指派。 Parent task only can edit task, create children and assign to somebody. */
- if((!empty($task->isParent)) && !in_array($action, array('edit', 'batchcreate', 'cancel', 'assignto', 'pause', 'close', 'restart', 'confirmstorychange', 'delete'))) return false;
- /* 子任务、多人任务、已取消已关闭的任务不能创建子任务。Multi task and child task and canceled/closed task cannot create children. */
- if($action == 'batchcreate' && (!empty($task->team) || !empty($task->mode) || !empty($task->rawParent) || in_array($task->status, array('closed', 'cancel')))) return false;
- /* 如果是IPD串行项目下的任务,则只有当前阶段开始以后才能开始/关闭任务。*/
- /* If it is a task under an IPD serial project, the task can be started / closed only after the current phase begins. */
- $executionInfo = zget($task, 'executionInfo', array());
- if(!empty($executionInfo->canStartExecution['disabled']) && in_array($action, array('start', 'finish', 'recordworkhour'))) return false;
- if(!empty($task->team))
- {
- global $app;
- $myself = new self();
- if($task->mode == 'linear')
- {
- if($action == 'assignto' && !in_array($task->status, array('done', 'cancel', 'closed'))) return false;
- if($action == 'start' && in_array($task->status, array('wait', 'doing')))
- {
- if($task->assignedTo != $app->user->account) return false;
- $currentTeam = $myself->getTeamByAccount($task->team, $app->user->account);
- if($currentTeam && $currentTeam->status == 'wait') return true;
- }
- if($action == 'finish' && $task->assignedTo != $app->user->account) return false;
- }
- elseif($task->mode == 'multi')
- {
- $currentTeam = $myself->getTeamByAccount($task->team, $app->user->account);
- if($action == 'start' && in_array($task->status, array('wait', 'doing')) && $currentTeam && $currentTeam->status == 'wait') return true;
- if($action == 'finish' && (empty($currentTeam) || $currentTeam->status == 'done')) return false;
- }
- }
- /* 根据状态判断是否可以点击。 Check clickable by status. */
- if($action == 'batchcreate') return (empty($task->team) || empty($task->children)) && zget($executionInfo, 'type') != 'kanban';
- if($action == 'start') return $task->status == 'wait';
- if($action == 'restart') return $task->status == 'pause';
- if($action == 'pause') return $task->status == 'doing';
- if($action == 'assignto') return !in_array($task->status, array('closed', 'cancel'));
- if($action == 'close') return $task->status == 'done' || $task->status == 'cancel' || ($task->isParent && $task->status != 'closed');
- if($action == 'activate') return $task->status == 'done' || $task->status == 'closed' || $task->status == 'cancel';
- if($action == 'finish') return $task->status != 'done' && $task->status != 'closed' && $task->status != 'cancel';
- if($action == 'cancel') return $task->status != 'done' && $task->status != 'closed' && $task->status != 'cancel';
- if($action == 'confirmstorychange') return !in_array($task->status, array('cancel', 'closed')) && !empty($task->needConfirm);
- return true;
- }
- /**
- * 检查执行是否有需求列表。
- * Check whether execution has story list.
- *
- * @param object $execution
- * @access public
- * @return bool
- */
- public function isNoStoryExecution($execution)
- {
- if(empty($execution)) return false;
- return $execution->lifetime == 'ops';
- }
- /**
- * 维护多人任务的团队信息。
- * Manage multi task team.
- *
- * @param string $mode
- * @param object $task
- * @param object $teamData
- * @access public
- * @return array|false
- */
- public function manageTaskTeam($mode, $task, $teamData)
- {
- /* Get old team member, and delete old task team. */
- $oldTeamData = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->eq($task->id)->fetchAll('account', false);
- $oldTeams = array_values($oldTeamData);
- $oldMembers = array_column($oldTeams, 'account');
- $this->dao->delete()->from(TABLE_TASKTEAM)->where('task')->eq($task->id)->exec();
- /* Set effort left = 0 when linear task members be changed. */
- $changeUsers = array();
- $teamData->team = array_filter($teamData->team);
- foreach($teamData->team as $index => $account)
- {
- if($mode == 'linear' && isset($oldTeams[$index]) && $oldTeams[$index]->account != $account) $changeUsers[] = $oldTeams[$index]->account;
- }
- /* Manage task team member. */
- $teams = $this->manageTaskTeamMember($mode, $task, $teamData, $oldTeamData);
- if(dao::isError()) return false;
- /* Set effort left = 0 when multi task members be removed. */
- if($mode == 'multi' && $oldMembers)
- {
- $removedMembers = array_diff($oldMembers, $teams);
- $changeUsers = array_merge($changeUsers, $removedMembers);
- }
- if($changeUsers) $this->taskTao->resetEffortLeft($task->id, $changeUsers);
- return $teams;
- }
- /**
- * 维护多人任务团队成员信息。
- * Manage multi-task team member information.
- *
- * @param string $mode
- * @param object $task
- * @param object $teamData
- * @param array $oldTeamData
- * @access public
- * @return false|array
- */
- public function manageTaskTeamMember($mode, $task, $teamData, $oldTeamData = array())
- {
- /* If status of the task is doing, get the person who did not complete the task. */
- $undoneUsers = array();
- if($task->status == 'doing')
- {
- $efforts = $this->getTaskEfforts($task->id);
- foreach($efforts as $effort)
- {
- if($effort->left != 0) $undoneUsers[$effort->account] = $effort->account;
- else unset($undoneUsers[$effort->account]);
- }
- }
- $minStatus = 'done';
- $teamList = array_filter($teamData->team);
- $teams = array();
- $order = 1;
- foreach($teamList as $index => $account)
- {
- /* Set member information. */
- $member = new stdclass();
- $member->task = $task->id;
- $member->order = $order;
- $member->account = $account;
- $member->estimate = isset($teamData->teamEstimate) ? (float)zget($teamData->teamEstimate, $index, 0.00) : 0.00;
- $member->consumed = isset($teamData->teamConsumed) ? (float)zget($teamData->teamConsumed, $index, 0.00) : 0.00;
- $member->left = isset($teamData->teamLeft) ? (float)zget($teamData->teamLeft, $index, 0.00) : 0.00;
- $member->status = isset($oldTeamData[$account]->status) ? $oldTeamData[$account]->status : 'wait';
- if($task->status == 'wait' && $member->estimate > 0 && $member->left == 0) $member->left = $member->estimate;
- if($task->status == 'done') $member->left = 0;
- if($member->status == 'done' && !empty($member->left)) $member->status = 'doing';
- /* Compute task status of member. */
- if($member->left == 0 && $member->consumed > 0)
- {
- $member->status = 'done';
- }
- elseif($task->status == 'doing')
- {
- $teamSource = zget($teamData->teamSource, $index);
- if(!empty($teamSource) && $teamSource != $account && isset($undoneUsers[$teamSource])) $member->transfer = $teamSource;
- if(isset($undoneUsers[$account]) && ($mode == 'multi' || ($mode == 'linear' && $minStatus != 'wait'))) $member->status = 'doing';
- }
- /* Compute multi-task status, and in a linear task, there is only one doing status. */
- if(($mode == 'linear' && $member->status == 'doing') || $member->status == 'wait') $minStatus = 'wait';
- if($minStatus != 'wait' && $member->status == 'doing') $minStatus = 'doing';
- /* Insert or update team member. */
- $this->taskTao->setTeamMember($member, $mode, isset($teams[$account]));
- if(dao::isError()) return false;
- $teams[$account] = $account;
- $order ++;
- }
- return $teams;
- }
- /**
- * 将默认图表设置与当前图表设置合并。
- * Merge the default chart settings and the settings of current chart.
- *
- * @param string $chartType
- * @access public
- * @return void
- */
- public function mergeChartOption($chartType)
- {
- $chartOption = $this->lang->task->report->$chartType;
- $commonOption = $this->lang->task->report->options;
- $chartOption->graph->caption = $this->lang->task->report->charts[$chartType];
- if(!isset($chartOption->type)) $chartOption->type = $commonOption->type;
- if(!isset($chartOption->width)) $chartOption->width = $commonOption->width;
- if(!isset($chartOption->height)) $chartOption->height = $commonOption->height;
- /* merge configuration */
- foreach($commonOption->graph as $key => $value)
- {
- if(isset($chartOption->graph->$key)) continue;
- $chartOption->graph->$key = $value;
- }
- }
- /**
- * 通过缺陷(Bug)导入的任务在完成时检查是否需要更新对应缺陷的状态。
- * When task is finished, check whether need update status of bug.
- *
- * @param object $task
- * @access public
- * @return bool
- */
- public function needUpdateBugStatus($task)
- {
- /* If the task is not imported from a bug, return false. */
- if($task->fromBug == 0) return false;
- /* If the bug has been resolved, return false. */
- $bug = $this->loadModel('bug')->getByID($task->fromBug);
- if($bug->status == 'resolved') return false;
- return true;
- }
- /**
- * 暂停一个任务。
- * Pause a task.
- *
- * @param object $task
- * @param array $output
- * @access public
- * @return array|bool
- */
- public function pause($task, $output = array())
- {
- /* Get old task. */
- $oldTask = $this->getById($task->id);
- /* Update kanban status. */
- $this->dao->update(TABLE_TASK)->data($task)->autoCheck()->checkFlow()->where('id')->eq($task->id)->exec();
- /* If task has parent task, update status of the parent task by the child task. */
- if($oldTask->isParent) $this->updateChildrenStatus($task->id);
- /* If output is not empty, update kanban cell. */
- $this->updateKanbanCell($task->id, $output, $oldTask->execution);
- if(dao::isError()) return false;
- return common::createChanges($oldTask, $task);
- }
- /**
- * 处理报表统计数据。
- * Process data for report.
- *
- * @param array $tasks
- * @param array $children
- * @param string $field execution|module|assignedTo|type|pri|deadline|estimate|left|consumed|finishedBy|closedReason|status|date
- * @access public
- * @return array
- */
- public function processData4Report($tasks, $children, $field)
- {
- if(!empty($children))
- {
- /* Remove the parent task from the tasks. */
- foreach($children as $childTask) unset($tasks[$childTask->parent]);
- }
- $fields = array();
- $dataList = array();
- foreach($tasks as $task)
- {
- $key = strpos(',estimate,consumed,left,', ",{$field},") !== false ? helper::formatHours($task->$field) : (string)$task->$field;
- if(!isset($fields[$key])) $fields[$key] = 0;
- $fields[$key] ++;
- }
- /* Process table statistics data. */
- if($field != 'date' && $field != 'deadline') asort($fields);
- foreach($fields as $field => $count)
- {
- $data = new stdclass();
- $data->name = $field;
- $data->value = $count;
- $dataList[$field] = $data;
- }
- return $dataList;
- }
- /**
- * 处理导出的任务信息。
- * Process export task information.
- *
- * @param array $tasks
- * @access protected
- * @return object[]
- */
- protected function processExportTasks($tasks)
- {
- /* Process multiple task info. */
- $taskTeam = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in(array_keys($tasks))->fetchGroup('task');
- $users = $this->loadModel('user')->getPairs('noletter');
- if(!empty($taskTeam))
- {
- foreach($taskTeam as $taskID => $team)
- {
- $tasks[$taskID]->team = $team;
- $tasks[$taskID]->estimate = '';
- $tasks[$taskID]->left = '';
- $tasks[$taskID]->consumed = '';
- foreach($team as $userInfo)
- {
- $tasks[$taskID]->estimate .= zget($users, $userInfo->account) . ':' . $userInfo->estimate . "\n";
- $tasks[$taskID]->left .= zget($users, $userInfo->account) . ':' . $userInfo->left . "\n";
- $tasks[$taskID]->consumed .= zget($users, $userInfo->account) . ':' . $userInfo->consumed . "\n";
- }
- }
- }
- /* Process parent-child task info. */
- $parentIdList = array();
- foreach($tasks as $task)
- {
- if($task->parent > 0) $parentIdList[] = $task->parent;
- }
- $parents = $this->dao->select('id, name')->from(TABLE_TASK)->where('id')->in($parentIdList)->fetchPairs();
- foreach($tasks as $task)
- {
- $task->parentId = $task->parent;
- $task->parent = zget($parents, $task->parent, '');
- }
- return $tasks;
- }
- /**
- * 处理任务,计算进度、获取相关信息。
- * Process a task, compute its progress and get its relates.
- *
- * @param object $task
- * @param bool $convertParent
- * @param array $workingDays
- * @access public
- * @return object
- */
- public function processTask($task, $convertParent = true, $workingDays = array())
- {
- $today = helper::today();
- /* Delayed or not?. */
- $this->loadModel('holiday');
- $isNotCancel = !in_array($task->status, array('cancel', 'closed')) || ($task->status == 'closed' && !helper::isZeroDate($task->finishedDate) && $task->closedReason != 'cancel');
- $isComputeDelay = $isNotCancel && !helper::isZeroDate($task->deadline);
- $workingDays = empty($workingDays) && $isComputeDelay ? $this->holiday->getActualWorkingDays($task->deadline, $today) : $workingDays;
- if($isComputeDelay) $task = $this->computeDelay($task, $task->deadline, $workingDays);
- /* Story changed or not. */
- $task->needConfirm = false;
- if(!empty($task->storyStatus) && !in_array($task->status, array('cancel', 'closed')) && $task->storyStatus == 'active' && $task->latestStoryVersion > $task->storyVersion)
- {
- $task->needConfirm = true;
- $task->rawStatus = $task->status;
- $task->status = 'changed';
- }
- /* Set product type for task. */
- if(!empty($task->product))
- {
- $product = $this->loadModel('product')->fetchByID($task->product);
- if($product) $task->productType = $product->type;
- }
- /* Get related test cases. */
- if($task->story) $task->cases = $this->dao->select('id, title')->from(TABLE_CASE)->where('story')->eq($task->story)->andWhere('storyVersion')->eq($task->storyVersion)->andWhere('deleted')->eq('0')->fetchPairs();
- /* Set realname to task.*/
- if($task->assignedTo != 'closed') $task->assignedToRealName = $this->dao->select('realname')->from(TABLE_USER)->where('account')->eq($task->assignedTo)->fetch('realname');
- /* Set closed realname. */
- if($task->assignedTo == 'closed') $task->assignedToRealName = 'Closed';
- $task->progress = $this->taskTao->computeTaskProgress($task);
- if($task->mode == 'multi')
- {
- $teamMembers = $this->dao->select('t1.realname')->from(TABLE_USER)->alias('t1')
- ->leftJoin(TABLE_TASKTEAM)->alias('t2')
- ->on('t1.account = t2.account')
- ->where('t2.task')->eq($task->id)
- ->fetchPairs();
- $task->teamMembers = implode(',', array_keys($teamMembers));
- }
- if($this->config->edition != 'open')
- {
- $fieldList = $this->loadModel('workflowfield')->getList('task');
- foreach($fieldList as $field)
- {
- if($field->type == 'date' || $field->type == 'datetime') $this->config->task->dateFields[] = $field->field;
- }
- }
- foreach($task as $field => $value)
- {
- if(in_array($field, $this->config->task->dateFields) && helper::isZeroDate($value)) $task->$field = null;
- }
- $task->rawParent = $task->parent;
- if($convertParent)
- {
- $task->parent = array();
- foreach(explode(',', trim((string)$task->path, ',')) as $parentID)
- {
- if(!$parentID) continue;
- if($parentID == $task->id) continue;
- $task->parent[] = (int)$parentID;
- }
- }
- return $task;
- }
- /**
- * 批量处理任务,计算进度、获取相关信息。
- * Batch process tasks, compute their progress and get their relates.
- *
- * @param array $tasks
- * @access public
- * @return array
- */
- public function processTasks($tasks)
- {
- $begin = $end = helper::today();
- foreach($tasks as $task)
- {
- if(helper::isZeroDate($task->deadline)) continue;
- $begin = $task->deadline < $begin ? $task->deadline : $begin;
- if(!empty($task->children))
- {
- foreach($task->children as $child)
- {
- if(helper::isZeroDate($child->deadline)) continue;
- $begin = $child->deadline < $begin ? $child->deadline : $begin;
- }
- }
- }
- $workingDays = $this->loadModel('holiday')->getActualWorkingDays($begin, $end);
- $storyVersionPairs = $this->getTeamStoryVersion(array_keys($tasks));
- foreach($tasks as &$task)
- {
- $task->storyVersion = zget($storyVersionPairs, $task->id, $task->storyVersion);
- $task = $this->processTask($task, false, $workingDays);
- if(!empty($task->children))
- {
- foreach($task->children as &$child) $child = $this->processTask($child, false, $workingDays);
- }
- }
- return $tasks;
- }
- /**
- * 记录工时。
- * Record workhour and left of task.
- *
- * @param int $taskID
- * @param array $workhour the form data from POST
- * @access public
- * @return array
- */
- public function recordWorkhour($taskID, $workhour)
- {
- $task = $this->fetchByID($taskID);
- $task = $this->taskTao->formatDatetime($task);
- $task->team = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->eq($taskID)->orderBy('order')->fetchAll('id', false);
- /* Check if field is valid. */
- $workhour = $this->checkWorkhour($task, $workhour);
- if(!$workhour || dao::isError()) return array();
- /* Add field to workhour. */
- $workhour = $this->taskTao->buildWorkhour($taskID, $workhour);
- if(empty($workhour)) return array();
- $allChanges = array();
- $oldStatus = $task->status;
- $lastDate = $this->dao->select('date')->from(TABLE_EFFORT)->where('objectID')->eq($taskID)->andWhere('objectType')->eq('task')->andWhere('deleted')->eq('0')->orderBy('date_desc,id_desc')->limit(1)->fetch('date');
- $currentTeam = !empty($task->team) ? $this->getTeamByAccount($task->team, $this->app->user->account, array()) : array();
- foreach($workhour as $record)
- {
- if(isset($record->consumed)) $record->consumed = round((float)$record->consumed, 2);
- if(isset($record->left)) $record->left = round((float)$record->left, 2);
- $this->addTaskEffort($record);
- $effortID = $this->dao->lastInsertID();
- $isFinishTask = (empty($currentTeam) && in_array($task->status, $this->config->task->unfinishedStatus)) || (!empty($currentTeam) && $currentTeam->status != 'done');
- /* Change the workhour and status of tasks through effort. */
- list($newTask, $actionID) = $this->taskTao->buildTaskForEffort($record, $task, (string)$lastDate, $isFinishTask);
- if($lastDate <= $record->date) $lastDate = $record->date;
- /* Process multi-person task. Update consumed on team table. */
- if(!empty($currentTeam))
- {
- $currentTeam->status = $record->left == 0 ? 'done' : 'doing';
- $this->taskTao->updateTeamByEffort($effortID, $record, $currentTeam, $task, (string)$lastDate);
- $newTask = $this->computeMultipleHours($task, $newTask);
- }
- $changes = common::createChanges($task, $newTask, 'task');
- if($changes && $actionID) $this->loadModel('action')->logHistory($actionID, $changes);
- if($changes) $allChanges = array_merge($allChanges, $changes);
- $task = $newTask;
- }
- /* Update task and do other operations. */
- if($allChanges)
- {
- if($this->config->edition != 'open')
- {
- $fieldList = $this->loadModel('workflowfield')->getList('task');
- foreach($fieldList as $field)
- {
- if($field->type == 'date' || $field->type == 'datetime') $this->config->task->dateFields[] = $field->field;
- }
- }
- foreach($this->config->task->dateFields as $field)
- {
- if(isset($task->$field) && helper::isZeroDate($task->$field)) $task->$field = null;
- }
- $this->dao->update(TABLE_TASK)->data($task, 'team')->where('id')->eq($taskID)->exec();
- if($task->parent > 0) $this->updateParentStatus($task->id);
- if($task->story) $this->loadModel('story')->setStage($task->story);
- if($task->status != $oldStatus)
- {
- $this->loadModel('kanban')->updateLane($task->execution, 'task', $taskID);
- if($this->config->edition != 'open' && $task->feedback) $this->loadModel('feedback')->updateStatus('task', $task->feedback, $task->status, $oldStatus, $task->id);
- }
- if($task->status == 'done' && !dao::isError()) $this->loadModel('score')->create('task', 'finish', $taskID);
- }
- $this->loadModel('program')->refreshProjectStats($task->project);
- return $allChanges;
- }
- /**
- * 检查录入日志的字段必填性及日志记录人要在多人任务的团队中。
- * Check that the required fields of the effort must be filled in and the effort recorder must be in the multi-task team.
- *
- * @param object $task
- * @param array $workhour
- * @access public
- * @return false|array
- */
- public function checkWorkhour($task, $workhour)
- {
- foreach($workhour as $id => $record)
- {
- if(!$record->work && !$record->consumed && !$record->left)
- {
- unset($workhour[$id]);
- continue;
- }
- $date = $record->date;
- $consumed = $record->consumed;
- $left = $record->left;
- /* Check the date of workhour. */
- if(helper::isZeroDate($date)) dao::$errors["date[$id]"] = $this->lang->task->error->dateEmpty;
- if($date > helper::today()) dao::$errors["date[$id]"] = 'ID #' . $id . ' ' . $this->lang->task->error->date;
- /* Check consumed hours. */
- if(!$consumed)
- {
- dao::$errors["consumed[$id]"] = $this->lang->task->error->consumedThisTime;
- }
- elseif(!is_numeric($consumed) && !empty($consumed))
- {
- dao::$errors["consumed[$id]"] = 'ID #' . $id . ' ' . $this->lang->task->error->totalNumber;
- }
- elseif(is_numeric($consumed) && $consumed <= 0)
- {
- dao::$errors["consumed[$id]"] = sprintf($this->lang->error->gt, 'ID #' . $id . ' ' . $this->lang->task->record, '0');
- }
- elseif(!$record->work && $this->config->edition != 'open')
- {
- dao::$errors["work[$id]"] = sprintf($this->lang->error->notempty, $this->lang->task->work);
- }
- /* Check left hours. */
- if($left === '') dao::$errors["left[$id]"] = $this->lang->task->error->left;
- if(!is_numeric($left)) dao::$errors["left[$id]"] = 'ID #' . $id . ' ' . $this->lang->task->error->leftNumber;
- if(is_numeric($left) && $left < 0) dao::$errors["left[$id]"] = sprintf($this->lang->error->gt, 'ID #' . $id . ' ' . $this->lang->task->left, '0');
- }
- if(dao::isError()) return false;
- $inTeam = $this->dao->select('id')->from(TABLE_TASKTEAM)->where('task')->eq($task->id)->andWhere('account')->eq($this->app->user->account)->fetch('id');
- if($task->team && !$inTeam) return false;
- return $workhour;
- }
- /**
- * 设置任务的附件。
- * Set attachments for tasks.
- *
- * @param mixed[] $taskIdList
- * @access public
- * @return bool
- */
- public function setTaskFiles($taskIdList)
- {
- if(empty($taskIdList)) return true;
- $taskID = array_shift($taskIdList);
- $taskFilePairs = $this->loadModel('file')->saveUpload('task', $taskID);
- if(!empty($taskIdList))
- {
- $taskFiles = $taskFilePairs ? $this->dao->select('*')->from(TABLE_FILE)->where('id')->in(array_keys($taskFilePairs))->fetchAll('id', false) : array();
- foreach($taskIdList as $objectID)
- {
- foreach($taskFiles as $taskFile)
- {
- $taskFile->objectID = $objectID;
- unset($taskFile->id);
- $this->dao->insert(TABLE_FILE)->data($taskFile)->exec();
- }
- }
- }
- return !dao::isError();
- }
- /**
- * 获取报表的查询语句。
- * Get report condition from session.
- *
- * @access public
- * @return string
- */
- public function reportCondition()
- {
- if(!empty($_SESSION['taskQueryCondition']))
- {
- if(!$this->session->taskOnlyCondition) return 'id in (' . preg_replace('/SELECT .* FROM/', 'SELECT t1.id FROM', (string)$this->session->taskQueryCondition) . ')';
- return $this->session->taskQueryCondition;
- }
- return '1=1';
- }
- /**
- * 开始一个任务。
- * Start a task.
- *
- * @param object $oldTask
- * @param object $task
- * @access public
- * @return false|array
- */
- public function start($oldTask, $task)
- {
- /* Process data for multiple tasks. */
- $account = $this->app->user->account;
- $currentTeam = !empty($oldTask->team) ? $this->getTeamByAccount($oldTask->team, $account, array()) : '';
- if($currentTeam)
- {
- /* Update task team. */
- $team = new stdclass();
- $team->consumed = round((float)$task->consumed, 2);
- $team->left = round((float)$task->left, 2);
- $team->status = empty($team->left) ? 'done' : 'doing';
- $this->dao->update(TABLE_TASKTEAM)->data($team)->where('id')->eq($currentTeam->id)->exec();
- /* Compute hours for multiple task. */
- $task = $this->computeMultipleHours($oldTask, $task);
- /* Set the assigner for the task. */
- $now = helper::now();
- if($team->status == 'done')
- {
- if(isset($oldTask->team[$currentTeam->id])) $oldTask->team[$currentTeam->id]->status = $team->status; // 通过开始直接完成的情况,status会变成done
- $task->assignedTo = $this->getAssignedTo4Multi($oldTask->team, $oldTask, 'current');
- $task->assignedDate = $now;
- }
- /* Set the task finisher. */
- $finishedUsers = $this->taskTao->getFinishedUsers($oldTask->id, array_keys($oldTask->members));
- if(count($finishedUsers) == count($oldTask->team))
- {
- $task->status = 'done';
- $task->finishedBy = $account;
- $task->finishedDate = $now;
- }
- }
- if(isset($task->consumed)) $task->consumed = round((float)$task->consumed, 2);
- if(isset($task->left)) $task->left = round((float)$task->left, 2);
- $this->dao->update(TABLE_TASK)->data($task)->autoCheck()->checkFlow()->where('id')->eq($oldTask->id)->exec();
- if(dao::isError()) return false;
- if($task->consumed != $oldTask->consumed || $task->left != $oldTask->left) $this->loadModel('program')->refreshProjectStats($oldTask->project);
- return common::createChanges($oldTask, $task);
- }
- /**
- * 更新一个任务。
- * Update a task.
- *
- * @param object $task
- * @param object $teamData
- * @access public
- * @return array|string|false
- */
- public function update($task, $teamData = null)
- {
- $taskID = $task->id;
- $oldTask = $this->getByID($taskID);
- $task->project = $oldTask->project;
- if($task->consumed < $oldTask->consumed)
- {
- dao::$errors[] = $this->lang->task->error->consumedSmall;
- return false;
- }
- /* Compute hours and manage team for multi-task. */
- if($teamData && !empty($teamData->team) && count(array_filter($teamData->team)) > 1)
- {
- $teams = $this->manageTaskTeam($task->mode, $task, $teamData);
- if(!empty($teams)) $task = $this->computeMultipleHours($oldTask, $task, array(), false);
- }
- $requiredFields = $this->taskTao->getRequiredFields4Edit($task);
- if(dao::isError()) return false;
- $this->taskTao->doUpdate($task, $oldTask, $requiredFields);
- if(dao::isError()) return false;
- if($task->estimate != $oldTask->estimate || $task->consumed != $oldTask->consumed || $task->left != $oldTask->left) $this->loadModel('program')->refreshProjectStats($oldTask->project);
- $this->afterUpdate($oldTask, $task);
- unset($oldTask->parent, $task->parent);
- /* Logging history when multi-task team members have changed. */
- if(!empty($oldTask->team) && !empty($teamData->team)) list($oldTask, $task) = $this->taskTao->createChangesForTeam($oldTask, $task);
- $this->loadModel('file')->processFileDiffsForObject('task', $oldTask, $task);
- $task = $this->file->replaceImgURL($task, 'desc');
- $changes = common::createChanges($oldTask, $task);
- /* Record log. */
- if($this->post->comment != '' || !empty($changes))
- {
- $extra = '';
- array_map(function($field) use (&$extra) { if($field['field'] == 'status') $extra = 'statuschanged'; }, $changes);
- $action = !empty($changes) ? 'Edited' : 'Commented';
- $actionID = $this->loadModel('action')->create('task', $taskID, $action, $this->post->comment, $extra);
- if(!empty($changes) && $actionID) $this->action->logHistory($actionID, $changes);
- }
- return $changes;
- }
- /**
- * 编辑任务的日志。
- * Update effort of task.
- *
- * @param object $effort
- * @access public
- * @return array|false
- */
- public function updateEffort($effort)
- {
- $oldEffort = $this->getEffortByID($effort->id);
- if(!$this->taskTao->checkEffort($effort)) return false;
- if(isset($effort->consumed)) $effort->consumed = round((float)$effort->consumed, 2);
- if(isset($effort->left)) $effort->left = round((float)$effort->left, 2);
- $this->dao->update(TABLE_EFFORT)->data($effort)
- ->autoCheck()
- ->where('id')->eq((int)$effort->id)
- ->exec();
- $this->loadModel('program')->refreshProjectStats($oldEffort->project);
- $task = $this->getById($oldEffort->objectID);
- $data = $this->buildTaskForUpdateEffort($task, $oldEffort, $effort);
- /* Process multi-task by effort. */
- if(!empty($task->team))
- {
- $currentTeam = $this->getTeamByAccount($task->team, $oldEffort->account, array('order' => $oldEffort->order));
- if($currentTeam)
- {
- $newTeamInfo = new stdclass();
- $newTeamInfo->consumed = $currentTeam->consumed + $effort->consumed - $oldEffort->consumed;
- if($currentTeam->status != 'done') $newTeamInfo->left = $data->left;
- if($currentTeam->status != 'done' && $newTeamInfo->consumed > 0 && $data->left == 0) $newTeamInfo->status = 'done';
- $this->dao->update(TABLE_TASKTEAM)->data($newTeamInfo)->where('id')->eq($currentTeam->id)->exec();
- $data = $this->computeMultipleHours($task, $data);
- }
- }
- $this->dao->update(TABLE_TASK)->data($data)->where('id')->eq($task->id)->exec();
- if(dao::isError()) return false;
- if($task->consumed != $data->consumed || $task->left != $data->left) $this->loadModel('program')->refreshProjectStats($task->project);
- if($task->parent > 0) $this->updateParentStatus($task->id);
- if($task->story) $this->loadModel('story')->setStage($task->story);
- $oldTask = new stdclass();
- $oldTask->consumed = $task->consumed;
- $oldTask->left = $task->left;
- $oldTask->status = $task->status;
- $oldTask->date = $oldEffort->date;
- $newTask = new stdclass();
- $newTask->consumed = $data->consumed;
- $newTask->left = $data->left;
- $newTask->status = $data->status;
- $newTask->date = $effort->date;
- return common::createChanges($oldTask, $newTask);
- }
- /**
- * 更新串行任务工时日志的排序。
- * Update effort order for linear task team.
- *
- * @param int $effortID
- * @param int $order
- * @access public
- * @return bool
- */
- public function updateEffortOrder($effortID, $order)
- {
- $this->dao->update(TABLE_EFFORT)->set('`order`')->eq($order)->where('id')->eq($effortID)->exec();
- return !dao::isError();
- }
- /**
- * 更新预计开始和结束日期。
- * Update estimate date by gantt.
- *
- * @param object $postData
- * @access public
- * @return bool
- */
- public function updateEsDateByGantt($postData)
- {
- $this->app->loadLang('project');
- $postData->endDate = date('Y-m-d', strtotime('-1 day', strtotime($postData->endDate)));
- $changeTable = $postData->type == 'task' ? TABLE_TASK : TABLE_PROJECT;
- $actionType = $postData->type == 'task' ? 'task' : 'execution';
- $oldObject = $this->dao->select('*')->from($changeTable)->where('id')->eq($postData->id)->fetch();
- if($postData->type == 'task')
- {
- $project = $this->dao->select('id,taskDateLimit')->from(TABLE_PROJECT)->where('id')->eq($oldObject->project)->fetch();
- if($project->taskDateLimit == 'limit')
- {
- $parentTasks = $this->dao->select('id,estStarted,deadline')->from(TABLE_TASK)->where('id')->in($oldObject->path)->andWhere('id')->ne($oldObject->id)->fetchAll('id');
- foreach(array_reverse(array_filter(explode(',', $oldObject->path))) as $taskID)
- {
- if(!isset($parentTasks[$taskID])) continue;
- $parentTask = $parentTasks[$taskID];
- if(!helper::isZeroDate($parentTask->estStarted) && $parentTask->estStarted > $postData->startDate) dao::$errors[] = sprintf($this->lang->task->overParentEsStarted, $parentTask->estStarted);
- if(!helper::isZeroDate($parentTask->deadline) && $parentTask->deadline < $postData->endDate) dao::$errors[] = sprintf($this->lang->task->overParentDeadline, $parentTask->deadline);
- if(dao::isError()) return false;
- }
- }
- $oldObject->estStarted = $postData->startDate;
- $oldObject->deadline = $postData->endDate;
- unset($oldObject->openedDate);
- unset($oldObject->assignedDate);
- unset($oldObject->realStarted);
- unset($oldObject->finishedDate);
- unset($oldObject->canceledDate);
- unset($oldObject->closedDate);
- unset($oldObject->lastEditedDate);
- unset($oldObject->activatedDate);
- $this->loadModel('task')->update($oldObject);
- }
- elseif($postData->type == 'plan')
- {
- $this->updateExecutionEsDateByGantt($postData);
- }
- if(dao::isError()) return false;
- $newObject = $this->dao->select('*')->from($changeTable)->where('id')->eq($postData->id)->fetch();
- $changes = common::createChanges($oldObject, $newObject);
- $actionID = $this->loadModel('action')->create($actionType, $postData->id, 'edited');
- if(!empty($changes) && $actionID) $this->loadModel('action')->logHistory($actionID, $changes);
- return true;
- }
- /**
- * 通过甘特图更新阶段的预计日期。
- * Update Execution estimate date by gantt.
- *
- * @param object $postData
- * @access public
- * @return bool
- */
- public function updateExecutionEsDateByGantt($postData)
- {
- $stage = $this->dao->select('project,parent,frozen')->from(TABLE_EXECUTION)->where('id')->eq($postData->id)->fetch();
- if(!empty($stage->frozen))
- {
- $this->app->loadLang('execution');
- dao::$errors[] = sprintf($this->lang->execution->stageFrozenTip, $this->lang->execution->ganttDrag);
- return false;
- }
- /* Get parent information. */
- $parentID = $stage->project != $stage->parent ? $stage->parent : 0;
- $parentData = $this->dao->select('begin,end')->from(TABLE_PROJECT)->where('id')->eq($parentID ? $parentID : $stage->project)->fetch();
- $begin = helper::isZeroDate($parentData->begin) ? '' : $parentData->begin;
- $end = helper::isZeroDate($parentData->end) ? '' : $parentData->end;
- $this->app->loadLang('programplan');
- $typeLang = $parentID ? $this->lang->programplan->parent : $this->lang->project->common;
- if(helper::diffDate($begin, $postData->startDate) > 0) dao::$errors[] = sprintf($this->lang->task->overEsStartDate, $typeLang, $typeLang);
- if(helper::diffDate($end, $postData->endDate) < 0) dao::$errors[] = sprintf($this->lang->task->overEsEndDate, $typeLang, $typeLang);
- if(dao::isError()) return false;
- $this->dao->update(TABLE_PROJECT)
- ->set('begin')->eq($postData->startDate)
- ->set('end')->eq($postData->endDate)
- ->set('lastEditedBy')->eq($this->app->user->account)
- ->where('id')->eq($postData->id)
- ->exec();
- return !dao::isError();
- }
- /**
- * 更新看板单元格。
- * Update kanban cell.
- *
- * @param int $taskID
- * @param array $output
- * @param int $executionID
- * @access public
- * @return void
- */
- public function updateKanbanCell($taskID, $output, $executionID)
- {
- if(!isset($output['toColID'])) $this->loadModel('kanban')->updateLane($executionID, 'task', $taskID);
- if(isset($output['toColID'])) $this->loadModel('kanban')->moveCard($taskID, (int)$output['fromColID'], (int)$output['toColID'], (int)$output['fromLaneID'], (int)$output['toLaneID']);
- }
- /**
- * 更新看板中的任务泳道数据。
- * Update the task lane data in Kanban.
- *
- * @param int $executionID
- * @param array $taskIdList
- * @param int $laneID
- * @param int $oldColumnID
- * @access public
- * @return bool
- */
- public function updateKanbanData($executionID, $taskIdList, $laneID, $oldColumnID)
- {
- if(!$executionID) return false;
- $this->loadModel('kanban');
- /* Get kanban id, lane id and column id. */
- $laneID = !empty($laneID) ? $laneID : 0;
- $columnID = $this->kanban->getColumnIDByLaneID($laneID, 'wait');
- if(empty($columnID)) $columnID = $oldColumnID;
- /* If both of lane id and column id are not empty, add task to the kanban cell. */
- if($laneID && $columnID)
- {
- foreach($taskIdList as $taskID) $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'task', (string)$taskID);
- }
- /* If lane id or column id is empty, update the task type lane of the kanban. */
- if(!$laneID || !$columnID) $this->kanban->updateLane($executionID, 'task');
- return true;
- }
- /**
- * 批量创建任务后的看板数据处理。
- * Kanban data processing after batch create tasks.
- *
- * @param int $taskID
- * @param int $executionID
- * @param int $laneID
- * @param int $columnID
- * @access public
- * @return bool
- */
- public function updateKanbanForBatchCreate($taskID, $executionID, $laneID, $columnID)
- {
- $this->loadModel('kanban');
- if($this->config->vision == 'lite')
- {
- $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'task', (string)$taskID);
- }
- else
- {
- $columnID = $this->kanban->getColumnIDByLaneID($laneID, 'wait');
- if(!empty($laneID) && !empty($columnID)) $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'task', (string)$taskID);
- }
- return !dao::isError();
- }
- /**
- * 拖动甘特图更新任务的顺序。
- * Update order by gantt.
- *
- * @param object $postData
- * @access public
- * @return bool
- */
- public function updateOrderByGantt($postData)
- {
- $order = 1;
- foreach($postData->tasks as $task)
- {
- $idList = explode('-', $task);
- $taskID = $idList[1];
- $this->dao->update(TABLE_TASK)->set('`order`')->eq($order)->where('id')->eq($taskID)->exec();
- $order ++;
- }
- return !dao::isError();
- }
- /**
- * 编辑任务时更新父任务的信息。
- * Update parent of a task.
- *
- * @param object $task
- * @param bool $isParentChanged
- * @access public
- * @return void
- */
- public function updateParent($task, $isParentChanged = false)
- {
- $oldTask = $this->fetchByID($task->id);
- $parentTask = empty($task->parent) ? null : $this->fetchByID((int)$task->parent);
- $parentPath = $parentTask ? $parentTask->path : ',';
- $path = $parentPath . $task->id . ',';
- $this->dao->update(TABLE_TASK)->set('path')->eq($path)->where('id')->eq($task->id)->exec();
- if($parentTask && !$parentTask->isParent) $this->dao->update(TABLE_TASK)->set('isParent')->eq(1)->where('id')->eq((int)$task->parent)->exec();
- /* 更新所有子任务的path. */
- $childIdList = $this->getAllChildId($task->id, false);
- if($childIdList)
- {
- $children = $this->getByIdList($childIdList);
- foreach($children as $child)
- {
- $newChildPath = str_replace($oldTask->path, $path, $child->path);
- $this->dao->update(TABLE_TASK)->set('path')->eq($newChildPath)->where('id')->eq($child->id)->exec();
- }
- }
- $this->updateParentStatus($task->id, $task->parent, !$isParentChanged);
- if($task->parent) $this->computeBeginAndEnd($task->parent);
- $this->taskTao->updateRelation((int)$task->id, (int)$task->parent);
- if($isParentChanged && $task->parent)
- {
- $this->loadModel('action')->create('task', $task->id, 'linkParentTask', '', $task->parent, '', false);
- $actionID = $this->action->create('task', $task->parent, 'linkChildTask', '', $task->id, '', false);
- $newParentTask = $this->fetchByID($task->parent);
- $changes = common::createChanges($parentTask, $newParentTask);
- if(!empty($changes) && $actionID) $this->action->logHistory($actionID, $changes);
- }
- }
- /**
- * 更新父任务的状态.
- * Update parent status by taskID of the child task.
- *
- * @param int $taskID
- * @param int $parentID
- * @param bool $createAction
- * @access public
- * @return void
- */
- public function updateParentStatus($taskID, $parentID = 0, $createAction = true)
- {
- /* Get child task info. */
- $childTask = $this->dao->select('id,assignedTo,parent,path,realStarted')->from(TABLE_TASK)->where('id')->eq($taskID)->fetch();
- if(empty($childTask)) return;
- $taskIdList = $childTask->path;
- if($parentID && $childTask->parent != $parentID) $taskIdList = $this->dao->select('id,assignedTo,parent,path')->from(TABLE_TASK)->where('id')->eq($parentID)->fetch('path');
- $taskIdList = array_unique(array_filter(explode(',', $taskIdList)));
- $parentTasks = $this->dao->select('*')->from(TABLE_TASK)->where('id')->in($taskIdList)->andWhere('id')->ne($taskID)->orderBy('path_desc')->fetchAll('id', false);
- if(empty($parentTasks)) return;
- $this->loadModel('story');
- foreach($parentTasks as $parentID => $parentTask)
- {
- /* Compute parent task hours and status. */
- $this->computeWorkingHours($parentID);
- $status = $this->taskTao->getParentStatusById($parentID);
- if(empty($status))
- {
- $this->dao->update(TABLE_TASK)->set('isParent')->eq(0)->where('id')->eq($parentID)->exec();
- continue;
- }
- /* 只有子任务是进行中和已完成的时候才更新父任务的状态。*/
- /* Update parent task status only child status is 'doing' or 'done'. */
- if(!in_array($status, array('doing', 'done'))) continue;
- if($parentTask->status == $status) continue;
- /* Update task status. */
- $this->taskTao->autoUpdateTaskByStatus($parentTask, $childTask, $status);
- if(dao::isError() || !$createAction) return;
- if($parentTask->story) $this->story->setStage($parentTask->story);
- /* Create action record. */
- $this->taskTao->createAutoUpdateTaskAction($parentTask, 'child');
- if($this->config->edition != 'open' && $parentTask->feedback) $this->loadModel('feedback')->updateStatus('task', $parentTask->feedback, $status, $parentTask->status, $parentID);
- }
- }
- /**
- * 更新子任务的状态.
- * Update children status by taskID.
- *
- * @param int $taskID
- * @param string $oldParentStatus
- * @access public
- * @return void
- */
- public function updateChildrenStatus($taskID, $oldParentStatus = '')
- {
- /* Get child task info. */
- $parentTask = $this->dao->select('id,status,isParent,parent,path')->from(TABLE_TASK)->where('id')->eq($taskID)->fetch();
- if(empty($parentTask)) return;
- $parentStatus = $parentTask->status;
- if(!in_array($parentStatus, array('doing', 'pause', 'cancel', 'closed'))) return;
- $childrenTasks = $this->dao->select('*')->from(TABLE_TASK)->where('path')->like("{$parentTask->path}%")->andWhere('id')->ne($taskID)->fetchAll('id', false);
- if(empty($childrenTasks)) return;
- $autoActions = array();
- if($parentTask->status == 'doing' && $oldParentStatus == 'pause') $autoActions = $this->dao->select('objectID,extra')->from(TABLE_ACTION)->where('objectType')->eq("task")->andWhere('objectID')->in(array_keys($childrenTasks))->andWhere('action')->eq('paused')->orderBy('date')->fetchAll('objectID');
- $this->loadModel('story');
- foreach($childrenTasks as $childID => $childTask)
- {
- if($childTask->status == $parentStatus) continue;
- if($parentStatus == 'pause' && $childTask->status != 'doing') continue;
- if($parentStatus == 'cancel' && in_array($childTask->status, array('done', 'closed'))) continue;
- if($parentStatus == 'doing' && $childTask->status != 'pause') continue;
- if(isset($autoActions[$childID]) && $autoActions[$childID]->extra != 'autobyparent') continue;
- $this->taskTao->autoUpdateTaskByStatus($childTask, null, $parentStatus);
- if(dao::isError()) return;
- if($childTask->story) $this->story->setStage($childTask->story);
- /* Create action record. */
- $this->taskTao->createAutoUpdateTaskAction($childTask, 'parent');
- if($this->config->edition != 'open' && $childTask->feedback) $this->loadModel('feedback')->updateStatus('task', $childTask->feedback, $status, $childTask->status, $childID);
- }
- }
- /**
- * 更新团队信息。
- * Update team.
- *
- * @param object $task
- * @param object $postData
- * @access public
- * @return array|false
- */
- public function updateTeam($task, $postData)
- {
- $taskID = $task->id;
- $oldTask = $this->getById($taskID);
- /* Check team data. */
- $team = array_filter($postData->team);
- foreach($team as $i => $account)
- {
- if($postData->teamConsumed[$i] == 0 && $postData->teamLeft[$i] == 0)
- {
- dao::$errors[] = $this->lang->task->noticeTaskStart;
- return false;
- }
- }
- if(count($team) <= 1)
- {
- dao::$errors[] = $this->lang->task->error->teamMember;
- return false;
- }
- /* Manage the team and calculate task work information. */
- $teams = $this->manageTaskTeam($oldTask->mode, $task, $postData);
- !empty($teams) ? $task = $this->computeMultipleHours($oldTask, $task) : $task->mode = '';
- /* Update parent task status. */
- if($oldTask->parent > 0) $this->updateParentStatus($taskID);
- $this->dao->update(TABLE_TASK)
- ->data($task)
- ->autoCheck()
- ->checkFlow()
- ->where('id')->eq($taskID)
- ->exec();
- if(dao::isError()) return false;
- if($task->estimate != $oldTask->estimate || $task->consumed != $oldTask->consumed || $task->left != $oldTask->left) $this->loadModel('program')->refreshProjectStats($oldTask->project);
- return common::createChanges($oldTask, $task);
- }
- /**
- * 通过任务ID获取任务团队信息。
- * Get the task team information through the task ID.
- *
- * @param int $taskID
- * @param string $orderBy
- * @access protected
- * @return object[]
- */
- public function getTeamByTask($taskID, $orderBy = 'order_asc')
- {
- return $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->eq($taskID)->orderBy($orderBy)->fetchAll('id', false);
- }
- /**
- * 更新任务关联的代码提交记录。
- * Update the commit logs linked with the tasks.
- *
- * @param int $taskID
- * @param int $repoID
- * @param array $revisions
- * @access public
- * @return bool
- */
- public function updateLinkedCommits($taskID, $repoID, $revisions)
- {
- if(!$taskID || !$repoID || empty($revisions)) return true;
- $task = $this->dao->select('project')->from(TABLE_TASK)->where('id')->eq($taskID)->fetch();
- if(!$task) return true;
- foreach($revisions as $revision)
- {
- $data = new stdclass();
- $data->product = 0;
- $data->project = $task->project;
- $data->AType = 'task';
- $data->AID = $taskID;
- $data->BType = 'commit';
- $data->BID = $revision;
- $data->relation = 'completedin';
- $data->extra = $repoID;
- $this->dao->replace(TABLE_RELATION)->data($data)->autoCheck()->exec();
- $data->AType = 'commit';
- $data->AID = $revision;
- $data->BType = 'task';
- $data->BID = $taskID;
- $data->relation = 'completedfrom';
- $this->dao->replace(TABLE_RELATION)->data($data)->autoCheck()->exec();
- }
- return !dao::isError();
- }
- /**
- * 获取Task关联的提交数据。
- * Get the commit data for the associated bugs
- * @param int $repoID
- * @param array $revisions
- * @access public
- * @return bool
- */
- public function getLinkedCommits($repoID, $revisions)
- {
- return $this->dao->select('t1.revision,t3.id AS id,t3.name AS name')
- ->from(TABLE_REPOHISTORY)->alias('t1')
- ->leftJoin(TABLE_RELATION)->alias('t2')->on("t2.relation='completedin' AND t2.BType='commit' AND t2.BID=t1.id")
- ->leftJoin(TABLE_TASK)->alias('t3')->on("t2.AType='task' AND t2.AID=t3.id")
- ->where('t1.revision')->in($revisions)
- ->andWhere('t1.repo')->eq($repoID)
- ->andWhere('t3.id')->notNULL()
- ->fetchGroup('revision', 'id');
- }
- /**
- * 获取执行未关闭的任务
- * Get unclosed tasks by execution
- *
- * @param mixed[]|int $execution
- * @access public
- * @return array|false
- */
- public function getUnclosedTasksByExecution($execution)
- {
- if(is_array($execution)) return $this->dao->select('id,execution')->from(TABLE_TASK)->where('execution')->in($execution)->andWhere('status')->ne('closed')->andWhere('deleted')->eq('0')->fetchGroup('execution');
- return $this->dao->select('id,name')->from(TABLE_TASK)->where('execution')->eq($execution)->andWhere('status')->ne('closed')->andWhere('deleted')->eq('0')->fetchPairs();
- }
- /**
- * 同步父任务的需求到子任务
- * Sync parent story to children
- *
- * @param object $task
- * @access public
- * @return bool
- */
- public function syncStoryToChildren($task)
- {
- $nonStoryTasks = $this->dao->select('id,story')->from(TABLE_TASK)
- ->where('path')->like("%,$task->id,%")
- ->andWhere('deleted')->eq('0')
- ->andWhere('story')->eq(0)
- ->andWhere('id')->ne($task->id)
- ->fetchPairs();
- $taskStory = $this->loadModel('story')->fetchByID($task->story);
- $this->loadModel('action');
- foreach($nonStoryTasks as $id => $story)
- {
- $this->dao->update(TABLE_TASK)->set('story')->eq($task->story)->set('storyVersion')->eq($taskStory->version)->where('id')->eq($id)->exec();
- $changes = array(array('field' => 'story', 'old' => $story, 'new' => $task->story, 'diff' => ''));
- $actionID = $this->action->create('task', $id, 'syncStoryByParentTask');
- $this->action->logHistory($actionID, $changes);
- }
- return !dao::isError();
- }
- /**
- * 获取子任务
- * Get child tasks
- *
- * @param array $taskIdList
- * @access public
- * @return array|false
- */
- public function getChildTasksByList($taskIdList)
- {
- $childTasks = $this->dao->select('id,parent,path,estStarted,deadline')->from(TABLE_TASK)->where('parent')->in($taskIdList)->andWhere('deleted')->eq('0')->fetchGroup('parent', 'id');
- $nonStoryChildTasks = $this->dao->select('id,parent')->from(TABLE_TASK)->where('parent')->in($taskIdList)->andWhere('story')->eq('0')->andWhere('deleted')->eq('0')->fetchGroup('parent', 'id');
- return array($childTasks, $nonStoryChildTasks);
- }
- /**
- * 确认需求变更。
- * Confirm story change.
- *
- * @param int $taskID
- * @access public
- * @return bool
- */
- public function confirmStoryChange($taskID)
- {
- $task = $this->getByID($taskID);
- if($task->storyVersion == $task->latestStoryVersion) return true;
- $this->dao->update(TABLE_TASK)->set('storyVersion')->eq($task->latestStoryVersion)->where('id')->eq($taskID)->exec();
- $this->dao->update(TABLE_TASKTEAM)->set('storyVersion')->eq($task->latestStoryVersion)->where('task')->eq($taskID)->andWhere('account')->eq($this->app->user->account)->exec();
- if(dao::isError()) return false;
- $this->loadModel('action')->create('task', $taskID, 'confirmed', '', $task->latestStoryVersion);
- return !dao::isError();
- }
- /**
- * 获取多人任务当前登录用户的需求版本。
- * Get team story version by current login user.
- *
- * @param int|array|string $taskIdList
- * @access public
- * @return array
- */
- public function getTeamStoryVersion($taskIdList)
- {
- return $this->dao->select('task,storyVersion')->from(TABLE_TASKTEAM)->where('task')->in($taskIdList)->andWhere('account')->eq($this->app->user->account)->fetchPairs();
- }
- /**
- * 处理需求确认变更按钮。
- * Process confirm story change button.
- *
- * @param object $task
- * @access public
- * @return object
- */
- public function processConfirmStoryChange($task)
- {
- if(empty($task->actions[0]['name']) || $task->actions[0]['name'] != 'confirmStoryChange') return $task;
- if(empty($task->team) && (empty($task->assignedTo) || $task->assignedTo == $this->app->user->account)) return $task;
- if(!empty($task->team))
- {
- $taskMembers = !empty($task->team) ? array_column($task->team, 'account') : array();
- $disabled = !in_array($this->app->user->account, $taskMembers);
- $task->actions[0]['disabled'] = $disabled;
- if($disabled) $task->actions[0]['hint'] = $this->lang->task->disabledHint->memberConfirmStoryChange;
- }
- else
- {
- $task->actions[0]['disabled'] = true;
- $task->actions[0]['hint'] = $this->lang->task->disabledHint->assignedConfirmStoryChange;
- }
- return $task;
- }
- /**
- * 计算任务延期。
- * Compute task delay.
- *
- * @param object $task
- * @param string $deadline
- * @param array $workingDays
- * @access public
- * @return object
- */
- public function computeDelay($task, $deadline = '', $workingDays = array())
- {
- if(helper::isZeroDate($deadline)) return $task;
- $today = helper::today();
- $endDate = helper::isZeroDate($task->finishedDate) ? $today : $task->finishedDate;
- $betweenDays = $this->loadModel('holiday')->getDaysBetween($deadline, $endDate);
- if($betweenDays)
- {
- $delayDays = array_intersect($betweenDays, $workingDays);
- $delay = !empty($delayDays) ? count($delayDays) - 1: 0;
- if($delay > 0) $task->delay = $delay;
- }
- return $task;
- }
- }
|