diff --git a/module/execution/control.php b/module/execution/control.php index fe810ea6a1..960445df0e 100644 --- a/module/execution/control.php +++ b/module/execution/control.php @@ -935,9 +935,6 @@ class execution extends control if($copyExecutionID) $this->executionZen->setFieldsByCopyExecution($execution, $copyExecutionID); $projectID = $execution->project; - $this->app->loadLang('program'); - $this->app->loadLang('stage'); - $this->app->loadLang('programplan'); if($executionID) return $this->executionZen->displayAfterCreated($projectID, $executionID, $planID, $confirm); $project = empty($projectID) ? null : $this->loadModel('project')->fetchByID($projectID); @@ -955,53 +952,36 @@ class execution extends control $_POST['plans'] = array_filter($_POST['plans']); } - $execution= $this->storyZen->buildExecutionForCreate(); + $execution = $this->executionZen->buildExecutionForCreate(); if(!$execution) return $this->send(array('result' => 'fail', 'message' => dao::getError())); $executionID = $this->execution->create($execution, isset($_POST['teamMembers']) ? $_POST['teamMembers'] : array()); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); - $this->execution->updateProducts($executionID, $postData); - if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); - - $comment = $execution->hasProduct ? implode(',', $_POST['products']) : ''; - $this->loadModel('action')->create($this->objectType, $executionID, 'opened', '', $comment); - + $this->execution->updateProducts($executionID, zget($_POST, 'products', array()), zget($_POST, 'plans', array()), zget($_POST, 'branch', array(0))); + $this->loadModel('action')->create($this->objectType, $executionID, 'opened', '', $execution->hasProduct ? implode(',', $_POST['products']) : ''); $this->loadModel('programplan')->computeProgress($executionID, 'create'); + if(!empty($projectID) and strpos(',kanban,agileplus,waterfallplus,', ",$project->model,") !== false and $execution->type == 'kanban') + { + $execution = $this->execution->fetchByID($executionID); + $this->loadModel('kanban')->createRDKanban($execution); + } $message = $this->executeHooks($executionID); - if($message) $this->lang->saveSuccess = $message; + if(empty($message)) $message = $this->lang->saveSuccess; - if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $executionID)); + if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $message, 'id' => $executionID)); + if($this->app->tab == 'doc') return $this->send(array('result' => 'success', 'message' => $message, 'load' => $this->createLink('doc', 'projectSpace', "objectID=$executionID"))); + if(!empty($_POST['plans'])) return $this->send(array('result' => 'success', 'message' => $message, 'load' => inlink('create', "projectID=$projectID&executionID=$executionID©ExecutionID=&planID=1&confirm=no"))); - if($this->app->tab == 'doc') + if(!empty($projectID) and $project->model == 'kanban') { - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $this->createLink('doc', 'projectSpace', "objectID=$executionID"))); - } + $link = $this->config->vision != 'lite' ? $this->createLink('project', 'index', "projectID=$projectID") : $this->createLink('project', 'execution', "status=all&projectID=$projectID"); + if($this->app->tab == 'project') return $this->send(array('result' => 'success', 'message' => $message, 'load' => $link)); - if(!empty($projectID) and strpos(',kanban,agileplus,waterfallplus,', ",$project->model,") !== false) - { - $execution = $this->execution->getById($executionID); - if($execution->type == 'kanban') $this->loadModel('kanban')->createRDKanban($execution); - } - - if(!empty($_POST['plans'])) - { - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => inlink('create', "projectID=$projectID&executionID=$executionID©ExecutionID=&planID=1&confirm=no"))); - } - else - { - if(!empty($projectID) and $project->model == 'kanban') - { - if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); - - $link = $this->config->vision != 'lite' ? $this->createLink('project', 'index', "projectID=$projectID") : $this->createLink('project', 'execution', "status=all&projectID=$projectID"); - if($this->app->tab == 'project') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $link)); - - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => inlink('kanban', "executionID=$executionID"))); - } - return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => inlink('create', "projectID=$projectID&executionID=$executionID"))); + return $this->send(array('result' => 'success', 'message' => $message, 'load' => inlink('kanban', "executionID=$executionID"))); } + return $this->send(array('result' => 'success', 'message' => $message, 'load' => inlink('create', "projectID=$projectID&executionID=$executionID"))); } list($this->view->pmUsers, $this->view->poUsers, $this->view->qdUsers, $this->view->rdUsers) = $this->executionZen->setUserMoreLink(); @@ -1023,7 +1003,6 @@ class execution extends control $this->view->from = $this->app->tab; $this->view->isStage = isset($project->model) && in_array($project->model, array('waterfall', 'waterfallplus')); $this->view->project = $project; - $this->view->stageBy = empty($project->stageBy) ? '' : $project->stageBy; $this->display(); } diff --git a/module/execution/css/create.ui.css b/module/execution/css/create.ui.css index 6592a4bbfb..2b44ba7876 100644 --- a/module/execution/css/create.ui.css +++ b/module/execution/css/create.ui.css @@ -14,4 +14,3 @@ #copyExecutions .btn.primary-outline {background-color: var(--color-primary-50);} #copyExecutions .btn.primary-outline:after {position: absolute; content: '\e92f'; font-family: ZentaoIcon; font-size: 20px; right: 25px;} #copyExecutions .btn.primary-outline span{color: rgba(var(--color-fore-rgb),var(--tw-text-opacity));} -#tipsModal.modal-no-backdrop {background-color: var(--modal-bg) !important; pointer-events: unset !important;} diff --git a/module/execution/css/tips.ui.css b/module/execution/css/tips.ui.css index e69de29bb2..ce3a1973fe 100644 --- a/module/execution/css/tips.ui.css +++ b/module/execution/css/tips.ui.css @@ -0,0 +1,2 @@ +#tipsModal {margin-top: 10%; max-width: 800px;} +#tipsModal .panel-heading {border-bottom:1px solid #e5e5e5; padding: 20px;} diff --git a/module/execution/js/create.ui.js b/module/execution/js/create.ui.js index 12114d3eb0..dad61062b5 100644 --- a/module/execution/js/create.ui.js +++ b/module/execution/js/create.ui.js @@ -1,11 +1,5 @@ $(function() { - if(executionID) - { - triggerTips(); - return false; - } - loadProjectExecutions(copyProjectID); if($('#methodHover').length) new zui.Tooltip('#methodHover', {title: methodTip, trigger: 'hover', placement: 'right', type: 'white', 'className': 'text-gray border border-light methodTip'}); @@ -140,15 +134,6 @@ function setCopyExecution() zui.Modal.hide(); } -window.triggerTips = function() -{ - const modal = zui.Modal.open({ - url: $.createLink('execution', 'tips', 'executionID=' + executionID), - id: 'tipsModal', - backdrop: false - }); -} - window.branchChange = function(e) { let $product = $(e.target).closest('.form-row').find("[name^='products']"); diff --git a/module/execution/model.php b/module/execution/model.php index 30e68ae946..c490d510b8 100755 --- a/module/execution/model.php +++ b/module/execution/model.php @@ -236,6 +236,7 @@ class executionModel extends model { $this->dao->insert(TABLE_EXECUTION)->data($execution) ->autoCheck('begin,end') + ->batchcheck($this->config->execution->create->requiredFields, 'notempty') ->checkIF(!empty($execution->name), 'name', 'unique', "`type` in ('sprint','stage', 'kanban') and `project` = " . (int)$execution->project . " and `deleted` = '0'") ->checkIF(!empty($execution->code), 'code', 'unique', "`type` in ('sprint','stage', 'kanban') and `deleted` = '0'") ->checkIF($execution->begin != '', 'begin', 'date') @@ -2326,16 +2327,17 @@ class executionModel extends model * Update products of a execution. * * @param int $executionID - * @param object $postData + * @param array $products + * @param array $plans + * @param array $branches * @access public - * @return void + * @return bool */ - public function updateProducts($executionID, object $postData) + public function updateProducts(int $executionID, array $products, array $plans, array $branches): bool { $this->loadModel('user'); - $products = $postData->products; - $oldProducts = $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->eq((int)$executionID)->fetchGroup('product', 'branch'); - $this->dao->delete()->from(TABLE_PROJECTPRODUCT)->where('project')->eq((int)$executionID)->exec(); + $oldProducts = $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($executionID)->fetchGroup('product', 'branch'); + $this->dao->delete()->from(TABLE_PROJECTPRODUCT)->where('project')->eq($executionID)->exec(); $members = array_keys($this->getTeamMembers($executionID)); if(empty($products)) { @@ -2343,8 +2345,6 @@ class executionModel extends model return true; } - $branches = isset($postData->branch) ? $postData->branch : array(0); - $plans = isset($postData->plans) ? $postData->plans : array(); $existedProducts = array(); foreach($products as $i => $productID) { @@ -2353,11 +2353,9 @@ class executionModel extends model $oldPlan = 0; $branch = isset($branches[$i]) ? (array) $branches[$i] : array(); - foreach($branch as $branchID) { if(isset($existedProducts[$productID][$branchID])) continue; - if(isset($oldProducts[$productID][$branchID])) { $oldProduct = $oldProducts[$productID][$branchID]; @@ -2366,7 +2364,7 @@ class executionModel extends model $data = new stdclass(); $data->project = $executionID; - $data->product = $productID; + $data->product = (int)$productID; $data->branch = (int)$branchID; $data->plan = isset($plans[$productID]) ? implode(',', $plans[$productID]) : $oldPlan; $data->plan = trim($data->plan, ','); @@ -2377,8 +2375,9 @@ class executionModel extends model } $oldProductKeys = array_keys($oldProducts); - $needUpdate = array_merge(array_diff($oldProductKeys, $products), array_diff($products, $oldProductKeys)); + $needUpdate = array_merge(array_diff($oldProductKeys, $products), array_diff($products, $oldProductKeys)); if($needUpdate) $this->user->updateUserView($needUpdate, 'product', $members); + return true; } /** @@ -2763,42 +2762,41 @@ class executionModel extends model */ public function linkStory(int $executionID, array $stories = array(), string $extra = '', array $lanes = array()): bool { - if(empty($executionID)) return false; - if(empty($stories)) return false; - - $this->loadModel('action'); - $this->loadModel('kanban'); - $versions = $this->loadModel('story')->getVersions($stories); - $linkedStories = $this->dao->select('story,`order`')->from(TABLE_PROJECTSTORY)->where('project')->eq($executionID)->orderBy('order_desc')->fetchPairs('story', 'order'); - $lastOrder = reset($linkedStories); - $storyList = $this->dao->select('id, status, branch, product')->from(TABLE_STORY)->where('id')->in(array_values($stories))->fetchAll('id'); - $execution = $this->getByID($executionID); + if(empty($executionID) || empty($stories)) return false; $extra = str_replace(array(',', ' '), array('&', ''), $extra); parse_str($extra, $output); + + $this->loadModel('action'); + $this->loadModel('kanban'); + $versions = $this->loadModel('story')->getVersions($stories); + $linkedStories = $this->dao->select('story,`order`')->from(TABLE_PROJECTSTORY)->where('project')->eq($executionID)->orderBy('order_desc')->fetchPairs('story', 'order'); + $lastOrder = (int)reset($linkedStories); + $storyList = $this->dao->select('id, status, branch, product')->from(TABLE_STORY)->where('id')->in(array_values($stories))->fetchAll('id'); + $execution = $this->fetchByID($executionID); + $notAllowedStatus = $this->app->rawMethod == 'batchcreate' ? 'closed' : 'draft,reviewing,closed'; + $laneID = isset($output['laneID']) ? $output['laneID'] : 0; + foreach($stories as $storyID) { if(isset($linkedStories[$storyID])) continue; - $notAllowedStatus = $this->app->rawMethod == 'batchcreate' ? 'closed' : 'draft,reviewing,closed'; if(strpos($notAllowedStatus, $storyList[$storyID]->status) !== false) continue; - $laneID = isset($output['laneID']) ? $output['laneID'] : 0; + $story = zget($storyList, $storyID, ''); + if(empty($story)) continue; if(!empty($lanes[$storyID])) $laneID = $lanes[$storyID]; $columnID = $this->kanban->getColumnIDByLaneID($laneID, 'backlog'); if(empty($columnID)) $columnID = isset($output['columnID']) ? $output['columnID'] : 0; - if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'story', $storyID); - $lastOrder ++; - $data = new stdclass(); $data->project = $executionID; - $data->product = (int)$storyList[$storyID]->product; - $data->branch = $storyList[$storyID]->branch; + $data->product = (int)$story->product; + $data->branch = $story->branch; $data->story = $storyID; $data->version = $versions[$storyID]; - $data->order = (int)$lastOrder; + $data->order = ++ $lastOrder; $this->dao->replace(TABLE_PROJECTSTORY)->data($data)->exec(); $this->story->setStage($storyID); @@ -2827,22 +2825,19 @@ class executionModel extends model { $this->loadModel('action'); $linkedCases = $this->dao->select('*')->from(TABLE_PROJECTCASE)->where('project')->eq($executionID)->orderBy('order_desc')->fetchPairs('case', 'order'); - $lastCaseOrder = empty($linkedCases) ? 0 : reset($linkedCases); + $lastCaseOrder = empty($linkedCases) ? 0 : (int)reset($linkedCases); $cases = $this->dao->select('id, version')->from(TABLE_CASE)->where('story')->eq($storyID)->fetchPairs(); $execution = $this->getByID($executionID); foreach($cases as $caseID => $version) { if(isset($linkedCases[$caseID])) continue; - $lastCaseOrder ++; - $object = new stdclass(); $object->project = $executionID; $object->product = $productID; $object->case = $caseID; $object->version = $version; - $object->order = $lastCaseOrder; - + $object->order = ++ $lastCaseOrder; $this->dao->insert(TABLE_PROJECTCASE)->data($object)->exec(); $action = $execution->type == 'project' ? 'linked2project' : 'linked2execution'; @@ -2860,39 +2855,29 @@ class executionModel extends model */ public function linkStories(int $executionID): bool { - $plans = $this->dao->select('product, plan')->from(TABLE_PROJECTPRODUCT) - ->where('project')->eq($executionID) - ->fetchPairs('product', 'plan'); - + $stories = array(); + $plans = $this->dao->select('product, plan')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($executionID)->fetchPairs('product', 'plan'); $projectID = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('project'); $this->session->set('project', $projectID); - $stories = array(); - $planProducts = array(); $this->loadModel('story'); $executionProducts = $this->loadModel('project')->getBranchesByProject($executionID); foreach($plans as $productID => $planIdList) { if(empty($planIdList)) continue; - $planIdList = explode(',', $planIdList); + $planIdList = array_filter(explode(',', $planIdList)); $executionBranches = zget($executionProducts, $productID, array()); foreach($planIdList as $planID) { $planStories = $this->story->getPlanStories($planID); - if(!empty($planStories)) + if(empty($planStories)) continue; + + foreach($planStories as $id => $story) { - foreach($planStories as $id => $story) - { - if($story->status != 'active' || (!empty($story->branch) && !empty($executionBranches) && !isset($executionBranches[$story->branch]))) - { - unset($planStories[$id]); - continue; - } - $planProducts[$story->id] = $story->product; - } - $stories = array_merge($stories, array_keys($planStories)); + if($story->status != 'active' || (!empty($story->branch) && !empty($executionBranches) && !isset($executionBranches[$story->branch]))) unset($planStories[$id]); } + $stories = array_merge($stories, array_keys($planStories)); } } diff --git a/module/execution/ui/create.html.php b/module/execution/ui/create.html.php index 51e8e850f4..d908cf9e1c 100644 --- a/module/execution/ui/create.html.php +++ b/module/execution/ui/create.html.php @@ -545,10 +545,5 @@ modalTrigger ) ); -modalTrigger -( - modal(set::id('tipsModal')) -); - /* ====== Render page ====== */ render(); diff --git a/module/execution/ui/tips.html.php b/module/execution/ui/tips.html.php index 416282c67d..48ecb1900c 100644 --- a/module/execution/ui/tips.html.php +++ b/module/execution/ui/tips.html.php @@ -10,53 +10,59 @@ declare(strict_types=1); */ namespace zin; -set::className('pt-6'); -set::closeBtn(array('url' => createLink('execution', 'create'), 'class' => 'ghost')); -div +panel ( - set::className('flex items-center'), - icon('check-circle text-success icon-2x mr-2'), - span + setID('tipsModal'), + set::title($lang->execution->tips), + set::headingActions(array ( - set::className('article-h2 tip-title'), - $lang->execution->afterInfo + array('url' => createLink('execution', 'task', "executionID={$executionID}"), 'icon' => 'close', 'class' => 'ghost') + )), + setClass('m-auto'), + div + ( + set::className('flex items-center'), + icon('check-circle text-success icon-2x mr-2'), + span + ( + set::className('article-h2 tip-title'), + $lang->execution->afterInfo + ) + ), + div + ( + btn + ( + set::className('mr-4 tipBtn ml-1'), + $lang->execution->setTeam, + set('data-url', createLink('execution', 'team', "executionID={$executionID}")), + ), + $execution->lifetime != 'ops' ? btn + ( + set::className('mr-4 tipBtn'), + $lang->execution->linkStory, + set('data-url', createLink('execution', 'linkstory', "executionID=$executionID")), + ) : null, + btn + ( + set::className('mr-4 tipBtn'), + $lang->execution->createTask, + set('data-url', createLink('task', 'create', "execution=$executionID")), + ), + btn + ( + set::className('mr-4 tipBtn'), + $lang->execution->goback, + set('data-url', createLink('execution', 'task', "executionID={$executionID}")), + ), + btn + ( + set::className('tipBtn'), + $lang->execution->gobackExecution, + set('data-url', createLink('execution', 'all')), + ), ) ); -div -( - set::className('my-4'), - btn - ( - set::className('mr-4 tipBtn ml-1'), - $lang->execution->setTeam, - set('data-url', createLink('execution', 'team', "executionID={$executionID}")), - ), - $execution->lifetime != 'ops' ? btn - ( - set::className('mr-4 tipBtn'), - $lang->execution->linkStory, - set('data-url', createLink('execution', 'linkstory', "executionID=$executionID")), - ) : null, - btn - ( - set::className('mr-4 tipBtn'), - $lang->execution->createTask, - set('data-url', createLink('task', 'create', "execution=$executionID")), - ), - btn - ( - set::className('mr-4 tipBtn'), - $lang->execution->goback, - set('data-url', createLink('execution', 'task', "executionID={$executionID}")), - ), - btn - ( - set::className('tipBtn'), - $lang->execution->gobackExecution, - set('data-url', createLink('execution', 'all')), - ), -); - /* ====== Render page ====== */ render(); diff --git a/module/execution/zen.php b/module/execution/zen.php index 397a52b450..8855f0565d 100644 --- a/module/execution/zen.php +++ b/module/execution/zen.php @@ -884,6 +884,7 @@ class executionZen extends execution $fields = $this->config->execution->form->create; $editorFields = array_keys(array_filter(array_map(function($config){return $config['control'] == 'editor';}, $fields))); foreach(explode(',', trim($this->config->execution->create->requiredFields, ',')) as $field) $fields[$field]['required'] = true; + if(!isset($_POST['code'])) $fields['code']['required'] = false; $this->config->execution->create->requiredFields = implode(',', array_keys(array_filter(array_map(function($config){return $config['required'] == true;}, $fields)))); $this->correctErrorLang(); @@ -900,7 +901,7 @@ class executionZen extends execution ->setIF($this->post->acl == 'open', 'whitelist', '') ->join('whitelist', ',') ->setDefault('type', $type) - ->stripTags($editorFields, $this->config->allowedTags) + ->stripTags(implode(',', $editorFields), $this->config->allowedTags) ->remove('products, workDays, delta, branch, uid, plans, teams, teamMembers, contactListMenu, heightType') ->get(); @@ -1953,7 +1954,8 @@ class executionZen extends execution $this->view->title = $this->lang->execution->tips; $this->view->executionID = $executionID; - $this->display(); + $this->view->execution = $this->execution->fetchByID($executionID); + $this->display('execution', 'tips'); } /**