870 lines
32 KiB
PHP
870 lines
32 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
/**
|
|
* The zen file of project module of ZenTaoPMS.
|
|
*
|
|
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
|
|
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
|
|
* @author sunguangming <sunguangming@easycorp.ltd>
|
|
* @link https://www.zentao.net
|
|
*/
|
|
class projectZen extends project
|
|
{
|
|
/**
|
|
* Append extras data to post data.
|
|
*
|
|
* @param object $postData
|
|
* @access protected
|
|
* @return int|object
|
|
*/
|
|
protected function prepareCreateExtras(object $postData): object
|
|
{
|
|
$rawdata = $postData->rawdata;
|
|
$project = $postData->setDefault('status', 'wait')
|
|
->setIF($rawdata->delta == 999, 'end', LONG_TIME)
|
|
->setIF($rawdata->delta == 999, 'days', 0)
|
|
->setIF($rawdata->acl == 'open', 'whitelist', '')
|
|
->setIF(!isset($rawdata->whitelist), 'whitelist', '')
|
|
->setIF(!isset($rawdata->multiple), 'multiple', '1')
|
|
->setDefault('openedBy', $this->app->user->account)
|
|
->setDefault('openedDate', helper::now())
|
|
->setDefault('team', $rawdata->name)
|
|
->setDefault('lastEditedBy', $this->app->user->account)
|
|
->setDefault('lastEditedDate', helper::now())
|
|
->setDefault('days', '0')
|
|
->add('type', 'project')
|
|
->join('whitelist', ',')
|
|
->join('auth', ',')
|
|
->stripTags($this->config->project->editor->create['id'], $this->config->allowedTags)
|
|
->get();
|
|
|
|
if(!isset($this->config->setCode) or $this->config->setCode == 0) unset($project->code);
|
|
|
|
/* Lean mode relation defaultProgram. */
|
|
if($this->config->systemMode == 'light') $project->parent = $this->config->global->defaultProgram;
|
|
|
|
if(!$this->checkProductAndBranch($project, $rawdata)) return false;
|
|
if(!$this->checkDaysAndBudget($project, $rawdata)) return false;
|
|
if(!$this->checkProductNameUnqiue($project, $rawdata)) return false;
|
|
|
|
return $project;
|
|
}
|
|
|
|
/**
|
|
* Append extras data to post data.
|
|
*
|
|
* @param object $postData
|
|
* @access protected
|
|
* @return int|object
|
|
*/
|
|
protected function prepareEditExtras(object $postData): object
|
|
{
|
|
$project = $postData->setDefault('team', $this->post->name)
|
|
->setDefault('lastEditedBy', $this->app->user->account)
|
|
->setDefault('lastEditedDate', helper::now())
|
|
->setDefault('days', '0')
|
|
->setIF($this->post->delta == 999, 'end', LONG_TIME)
|
|
->setIF($this->post->delta == 999, 'days', 0)
|
|
->setIF($this->post->begin == '0000-00-00', 'begin', '')
|
|
->setIF($this->post->end == '0000-00-00', 'end', '')
|
|
->setIF($this->post->future, 'budget', 0)
|
|
->setIF($this->post->budget != 0, 'budget', round((float)$this->post->budget, 2))
|
|
->stripTags($this->config->project->editor->edit['id'], $this->config->allowedTags)
|
|
->get();
|
|
|
|
if(!isset($this->config->setCode) or $this->config->setCode == 0) unset($project->code);
|
|
|
|
/* Lean mode relation defaultProgram. */
|
|
if($this->config->systemMode == 'light') $project->parent = $this->config->global->defaultProgram;
|
|
|
|
return $project;
|
|
}
|
|
|
|
/**
|
|
* Check product and branch not empty.
|
|
*
|
|
* @param object $project
|
|
* @param object $rawdata
|
|
* @access protected
|
|
* @return bool
|
|
*/
|
|
private function checkProductAndBranch(object $project, object $rawdata): bool
|
|
{
|
|
$linkedProductsCount = $this->project->getLinkedProductsCount($project, $rawdata);
|
|
|
|
if($rawdata->products)
|
|
{
|
|
$topProgramID = $this->loadModel('program')->getTopByID($project->parent);
|
|
$multipleProducts = $this->loadModel('product')->getMultiBranchPairs($topProgramID);
|
|
foreach($rawdata->products as $index => $productID)
|
|
{
|
|
if(isset($multipleProducts[$productID]) and empty($rawdata->branch[$index]))
|
|
{
|
|
dao::$errors[] = $this->lang->project->error->emptyBranch;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Judge products not empty. */
|
|
if($project->parent and $project->hasProduct && empty($linkedProductsCount) and !isset($rawdata->newProduct))
|
|
{
|
|
dao::$errors['products0'] = $this->lang->project->error->productNotEmpty;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check days and budget by rules.
|
|
*
|
|
* @param object $project
|
|
* @param object $rawdata
|
|
* @access protected
|
|
* @return bool
|
|
*/
|
|
private function checkDaysAndBudget(object $project, object $rawdata): bool
|
|
{
|
|
/* Judge workdays is legitimate. */
|
|
$workdays = helper::diffDate($project->end, $project->begin) + 1;
|
|
if(isset($project->days) and $project->days > $workdays)
|
|
{
|
|
dao::$errors['days'] = sprintf($this->lang->project->workdaysExceed, $workdays);
|
|
return false;
|
|
}
|
|
|
|
if(!empty($project->budget))
|
|
{
|
|
if(!is_numeric($project->budget))
|
|
{
|
|
dao::$errors['budget'] = sprintf($this->lang->project->error->budgetNumber);
|
|
return false;
|
|
}
|
|
elseif(is_numeric($project->budget) and ($project->budget < 0))
|
|
{
|
|
dao::$errors['budget'] = sprintf($this->lang->project->error->budgetGe0);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
$project->budget = round((float)$rawdata->budget, 2);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check product name unique and not empty.
|
|
*
|
|
* @param object $project
|
|
* @param object $rawdata
|
|
* @access protected
|
|
* @return bool
|
|
*/
|
|
private function checkProductNameUnqiue(object $project, object $rawdata): bool
|
|
{
|
|
/* When select create new product, product name cannot be empty and duplicate. */
|
|
if($project->hasProduct && isset($rawdata->newProduct))
|
|
{
|
|
if(empty($rawdata->productName))
|
|
{
|
|
$this->app->loadLang('product');
|
|
dao::$errors['productName'] = sprintf($this->lang->error->notempty, $this->lang->product->name);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
$programID = isset($project->parent) ? $project->parent : 0;
|
|
$existProductName = $this->dao->select('name')->from(TABLE_PRODUCT)->where('name')->eq($rawdata->productName)->andWhere('program')->eq($programID)->fetch('name');
|
|
if(!empty($existProductName))
|
|
{
|
|
dao::$errors['productName'] = $this->lang->project->error->existProductName;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Send variables to create page.
|
|
*
|
|
* @param string $model
|
|
* @param int $programID
|
|
* @param int $copyProjectID
|
|
* @param string $extra
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function buildCreateForm(string $model, int $programID, int $copyProjectID, string $extra): void
|
|
{
|
|
$this->loadModel('product');
|
|
$this->loadModel('program');
|
|
|
|
$extra = str_replace(array(',', ' '), array('&', ''), $extra);
|
|
parse_str($extra, $output);
|
|
|
|
if($this->app->tab == 'program' and $programID) $this->loadModel('program')->setMenu($programID);
|
|
if($this->app->tab == 'product' and !empty($output['productID'])) $this->loadModel('product')->setMenu($output['productID']);
|
|
if($this->app->tab == 'doc') unset($this->lang->doc->menu->project['subMenu']);
|
|
|
|
if($copyProjectID) $copyProject = $this->getCopyProject((int)$copyProjectID);
|
|
$shadow = empty($copyProject->hasProduct) ? 1 : 0;
|
|
|
|
if($this->view->globalDisableProgram) $programID = $this->config->global->defaultProgram;
|
|
$topProgramID = $this->program->getTopByID($programID);
|
|
|
|
if($model == 'kanban')
|
|
{
|
|
$this->lang->project->aclList = $this->lang->project->kanbanAclList;
|
|
$this->lang->project->subAclList = $this->lang->project->kanbanSubAclList;
|
|
}
|
|
|
|
$withProgram = $this->config->systemMode == 'ALM' ? true : false;
|
|
$allProducts = array('0' => '') + $this->program->getProductPairs($programID, 'all', 'noclosed', '', $shadow, $withProgram);
|
|
$parentProgram = $this->loadModel('program')->getByID($programID);
|
|
|
|
$this->view->title = $this->lang->project->create;
|
|
$this->view->gobackLink = (isset($output['from']) and $output['from'] == 'global') ? $this->createLink('project', 'browse') : '';
|
|
$this->view->model = $model;
|
|
$this->view->pmUsers = $this->loadModel('user')->getPairs('noclosed|nodeleted|pmfirst');
|
|
$this->view->users = $this->user->getPairs('noclosed|nodeleted');
|
|
$this->view->programID = $programID;
|
|
$this->view->productID = isset($output['productID']) ? $output['productID'] : 0;
|
|
$this->view->branchID = isset($output['branchID']) ? $output['branchID'] : 0;
|
|
$this->view->allProducts = $allProducts;
|
|
$this->view->multiBranchProducts = $this->product->getMultiBranchPairs($topProgramID);
|
|
$this->view->copyProjects = $this->project->getPairsByModel($model);
|
|
$this->view->copyProjectID = $copyProjectID;
|
|
$this->view->parentProgram = $parentProgram;
|
|
$this->view->programList = $this->program->getParentPairs();
|
|
$this->view->URSRPairs = $this->loadModel('custom')->getURSRPairs();
|
|
$this->view->availableBudget = $this->program->getBudgetLeft($parentProgram);
|
|
$this->view->budgetUnitList = $this->project->getBudgetUnitList();
|
|
|
|
$this->display();
|
|
}
|
|
|
|
/**
|
|
* Get copy project and send variables to create page.
|
|
*
|
|
* @param int $copyProjectID
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
private function getCopyProject(int $copyProjectID): object
|
|
{
|
|
$copyProject = $this->project->getByID($copyProjectID);
|
|
$products = $this->product->getProducts($copyProjectID);
|
|
|
|
foreach($products as $product)
|
|
{
|
|
$branches = implode(',', $product->branches);
|
|
$copyProject->productPlans[$product->id] = $this->loadModel('productplan')->getPairs($product->id, $branches, 'noclosed', true);
|
|
}
|
|
|
|
$this->view->branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products), 'noclosed');
|
|
$this->view->products = $products;
|
|
$this->view->copyProject = $copyProject;
|
|
|
|
return $copyProject;
|
|
}
|
|
|
|
/**
|
|
* Link plan's stories after create a project.
|
|
*
|
|
* @param object $postData
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function linkPlanStories(object $postData)
|
|
{
|
|
$planIdList = array();
|
|
foreach($postData->rawdata->plans as $plans)
|
|
{
|
|
foreach($plans as $planID)
|
|
{
|
|
$planIdList[$planID] = $planID;
|
|
}
|
|
}
|
|
|
|
$planStoryGroup = $this->loadModel('story')->getStoriesByPlanIdList($planIdList);
|
|
foreach($planIdList as $planID)
|
|
{
|
|
$planStories = $planProducts = array();
|
|
$planStory = isset($planStoryGroup[$planID]) ? $planStoryGroup[$planID] : array();
|
|
if(!empty($planStory))
|
|
{
|
|
foreach($planStory as $id => $story)
|
|
{
|
|
if($story->status == 'draft' or $story->status == 'reviewing')
|
|
{
|
|
unset($planStory[$id]);
|
|
continue;
|
|
}
|
|
$planProducts[$story->id] = $story->product;
|
|
}
|
|
|
|
$planStories = array_keys($planStory);
|
|
$this->loadModel('execution')->linkStory($projectID, $planStories, $planProducts);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Append extras data to post data.
|
|
*
|
|
* @param object $postData
|
|
* @access protected
|
|
* @return int|object
|
|
*/
|
|
protected function prepareStartExtras(object $postData): object
|
|
{
|
|
return $postData->add('status', 'doing')
|
|
->add('lastEditedBy', $this->app->user->account)
|
|
->add('lastEditedDate', helper::now())
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* Append extras data to post data.
|
|
*
|
|
* @param int $iprojectID
|
|
* @param object $postData
|
|
*
|
|
* @access protected
|
|
* @return object
|
|
*/
|
|
protected function prepareSuspendExtras(int $projectID, object $postData): object
|
|
{
|
|
$editorIdList = $this->config->project->editor->suspend['id'];
|
|
|
|
return $postData->add('id', $projectID)
|
|
->setDefault('status', 'suspended')
|
|
->setDefault('lastEditedBy', $this->app->user->account)
|
|
->setDefault('lastEditedDate', helper::now())
|
|
->setDefault('suspendedDate', helper::today())
|
|
->stripTags($editorIdList, $this->config->allowedTags)
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* Append extras data to post data.
|
|
*
|
|
* @param int $iprojectID
|
|
* @param object $postData
|
|
*
|
|
* @access protected
|
|
* @return object
|
|
*/
|
|
protected function prepareClosedExtras(int $projectID, object $postData): object
|
|
{
|
|
$editorIdList = $this->config->project->editor->suspend['id'];
|
|
|
|
return $postData->add('id', $projectID)
|
|
->setDefault('status', 'closed')
|
|
->setDefault('closedBy', $this->app->user->account)
|
|
->setDefault('closedDate', helper::now())
|
|
->setDefault('lastEditedBy', $this->app->user->account)
|
|
->setDefault('lastEditedDate', helper::now())
|
|
->stripTags($editorIdList, $this->config->allowedTags)
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* Send variables to view page.
|
|
*
|
|
* @param object $project
|
|
* @access protected
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function buildStartForm(object $project): void
|
|
{
|
|
$this->view->title = $this->lang->project->start;
|
|
$this->view->project = $project;
|
|
$this->view->users = $this->loadModel('user')->getPairs('noletter');
|
|
$this->view->actions = $this->loadModel('action')->getList('project', $project->id);
|
|
$this->display();
|
|
}
|
|
|
|
/**
|
|
* After starting the project, do other operations.
|
|
*
|
|
* @param object $project
|
|
* @param array $changes
|
|
* @param string $comment
|
|
*
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function responseAfterStart(object $project, array $changes, string $comment): void
|
|
{
|
|
if($comment != '' or !empty($changes))
|
|
{
|
|
$actionID = $this->loadModel('action')->create('project', $project->id, 'Started', $comment);
|
|
$this->action->logHistory($actionID, $changes);
|
|
}
|
|
|
|
$this->loadModel('common')->syncPPEStatus($project->id);
|
|
|
|
$this->executeHooks($project->id);
|
|
}
|
|
|
|
/**
|
|
* After suspending the project, do other operations.
|
|
*
|
|
* @param int $projectID
|
|
* @param array $changes
|
|
* @param string $comment
|
|
*
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function responseAfterSuspend(int $projectID, array $changes, string $comment): void
|
|
{
|
|
if($comment != '' or !empty($changes))
|
|
{
|
|
$actionID = $this->loadModel('action')->create('project', $projectID, 'Suspended', $comment);
|
|
$this->action->logHistory($actionID, $changes);
|
|
}
|
|
|
|
$this->loadModel('common')->syncPPEStatus($projectID);
|
|
|
|
$this->executeHooks($projectID);
|
|
}
|
|
|
|
/**
|
|
* Send variables to suspend page.
|
|
*
|
|
* @param int $projectID
|
|
* @access protected
|
|
*/
|
|
protected function buildSuspendForm(int $projectID): void
|
|
{
|
|
$this->view->title = $this->lang->project->suspend;
|
|
$this->view->users = $this->loadModel('user')->getPairs('noletter');
|
|
$this->view->actions = $this->loadModel('action')->getList('project', $projectID);
|
|
$this->view->project = $this->project->getByID($projectID);
|
|
$this->display();
|
|
}
|
|
|
|
/**
|
|
* After closing the project, do other operations.
|
|
*
|
|
* @param int $projectID
|
|
* @param array $changes
|
|
* @param string $comment
|
|
*
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function responseAfterClose(int $projectID, array $changes, string $comment): void
|
|
{
|
|
if($comment != '' or !empty($changes))
|
|
{
|
|
$actionID = $this->loadModel('action')->create('project', $projectID, 'Closed', $comment);
|
|
$this->action->logHistory($actionID, $changes);
|
|
}
|
|
|
|
$this->loadModel('common')->syncPPEStatus($projectID);
|
|
|
|
$this->executeHooks($projectID);
|
|
}
|
|
|
|
/**
|
|
* Send variables to close page.
|
|
*
|
|
* @param int $projectID
|
|
* @access protected
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function buildClosedForm(int $projectID): void
|
|
{
|
|
$this->view->title = $this->lang->project->close;
|
|
$this->view->users = $this->loadModel('user')->getPairs('noletter');
|
|
$this->view->project = $this->project->getByID($projectID);
|
|
$this->view->actions = $this->loadModel('action')->getList('project', $projectID);
|
|
$this->display();
|
|
}
|
|
|
|
/**
|
|
* 获取项目下拉选择框
|
|
* Get project drop menu.
|
|
*
|
|
* @param int $projectID
|
|
* @param int $module
|
|
* @param int $method
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function getDropMenu(int $projectID, string $module, string $method) :void
|
|
{
|
|
$this->loadModel('program');
|
|
|
|
$programs = array();
|
|
$orderedProjects = array();
|
|
|
|
$projects = $this->project->getListByCurrentUser();
|
|
$programs = $this->program->getPairs(true);
|
|
$link = $this->project->getProjectLink($module, $method, $projectID);
|
|
|
|
foreach($projects as $project)
|
|
{
|
|
$project->parent = $this->program->getTopByID($project->parent);
|
|
$project->parent = isset($programs[$project->parent]) ? $project->parent : $project->id;
|
|
$orderedProjects[$project->parent][] = $project;
|
|
unset($projects[$project->id]);
|
|
}
|
|
|
|
$this->view->link = $link;
|
|
$this->view->projectID = $projectID;
|
|
$this->view->projects = $orderedProjects;
|
|
$this->view->module = $module;
|
|
$this->view->method = $method;
|
|
$this->view->programs = $programs;
|
|
|
|
$this->display();
|
|
}
|
|
|
|
/**
|
|
* Send variables to activate page.
|
|
*
|
|
* @param object $project
|
|
* @access protected
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function buildActivateForm(object $project): void
|
|
{
|
|
$newBegin = date('Y-m-d');
|
|
$dateDiff = helper::diffDate($newBegin, $project->begin);
|
|
$dateTime = (int)(strtotime($project->end) + $dateDiff * 24 * 3600);
|
|
$newEnd = date('Y-m-d', $dateTime);
|
|
|
|
$this->view->title = $this->lang->project->activate;
|
|
$this->view->users = $this->loadModel('user')->getPairs('noletter');
|
|
$this->view->actions = $this->loadModel('action')->getList('project', $project->id);
|
|
$this->view->newBegin = $newBegin;
|
|
$this->view->newEnd = $newEnd;
|
|
$this->view->project = $project;
|
|
$this->display();
|
|
}
|
|
|
|
/**
|
|
* After activateing the project, do other operations.
|
|
*
|
|
* @param int $projectID
|
|
* @param array $changes
|
|
*
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function responseAfterActivate(int $projectID, array $changes): void
|
|
{
|
|
if($this->post->comment != '' or !empty($changes))
|
|
{
|
|
$actionID = $this->loadModel('action')->create('project', $projectID, 'Activated', $this->post->comment);
|
|
$this->action->logHistory($actionID, $changes);
|
|
}
|
|
|
|
$this->executeHooks($projectID);
|
|
}
|
|
|
|
/**
|
|
* Append extras data to post data.
|
|
*
|
|
* @param int $iprojectID
|
|
* @param object $postData
|
|
*
|
|
* @access protected
|
|
* @return object
|
|
*/
|
|
protected function prepareActivateExtras(int $projectID, object $postData): object
|
|
{
|
|
$rawdata = $postData->rawdata;
|
|
$oldProject = $this->project->getByID($projectID);
|
|
$editorIdList = $this->config->project->editor->activate['id'];
|
|
|
|
return $postData->add('id', $projectID)
|
|
->setDefault('realEnd', $oldProject->realEnd)
|
|
->setDefault('status', 'doing')
|
|
->setDefault('lastEditedBy', $this->app->user->account)
|
|
->setDefault('lastEditedDate', helper::now())
|
|
->setIF($rawdata->begin == '0000-00-00', 'begin', '')
|
|
->setIF($rawdata->end == '0000-00-00', 'end', '')
|
|
->setIF(!helper::isZeroDate($oldProject->realBegan), 'realBegan', helper::today())
|
|
->stripTags($editorIdList, $this->config->allowedTags)
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* 从项目中删除所有关联的执行。
|
|
* removes all associated executions from the be deleted project
|
|
*
|
|
* @param array $executionIdList
|
|
*
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function removeAssociatedExecutions(array $executionIdList): void
|
|
{
|
|
$this->project->deleteByTableName('zt_execution', array_keys($executionIdList));
|
|
foreach($executionIdList as $executionID => $execution) $this->loadModel('action')->create('execution', $executionID, 'deleted', '', ACTIONMODEL::CAN_UNDELETED);
|
|
$this->loadModel('user')->updateUserView($executionIdList, 'sprint');
|
|
}
|
|
|
|
/**
|
|
* 从项目中删除所有关联的产品。
|
|
* removes all associated products from the be deleted project
|
|
*
|
|
* @param object $project
|
|
*
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function removeAssociatedProducts(object $project): void
|
|
{
|
|
/* Delete shadow product.*/
|
|
if(!$project->hasProduct)
|
|
{
|
|
$productID = $this->loadModel('product')->getProductIDByProject($project->id);
|
|
$this->project->deleteByTableName('zt_product', $productID);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 关联产品时,合并项目下的新旧产品
|
|
* Merge old and new products under the project When link products.
|
|
*
|
|
* @param int $projectID
|
|
* @param object $project
|
|
* @param array $idList
|
|
* @param array $postData
|
|
*
|
|
* @access protected
|
|
* @return bool
|
|
*/
|
|
protected function mergeProducts(int $projectID, object $project, array $idList, array $postData): bool
|
|
{
|
|
$oldProducts = $this->loadModel('product')->getProducts($projectID);
|
|
$this->project->updateProducts($projectID);
|
|
if(dao::isError()) return false;
|
|
|
|
/* Merge old and new linked products under the project. */
|
|
$newProducts = $this->product->getProducts($projectID);
|
|
$oldProductIDs = array_keys($oldProducts);
|
|
$newProductIDs = array_keys($newProducts);
|
|
$diffProducts = array_merge(array_diff($oldProductIDs, $newProductIDs), array_diff($newProductIDs, $oldProductIDs));
|
|
if($diffProducts) $this->loadModel('action')->create('project', $projectID, 'Managed', '', !empty($postData->rawdata->products) ? implode(',', $postData->rawdata->products) : '');
|
|
|
|
/* Multiple and Division project update linked products. */
|
|
if(empty($project->multiple))
|
|
{
|
|
$executionID = $this->loadModel('execution')->getNoMultipleID($projectID);
|
|
if($executionID) $this->execution->updateProducts($executionID);
|
|
}
|
|
|
|
if(empty($project->division))
|
|
{
|
|
foreach($idList as $executionID)
|
|
{
|
|
$this->execution->updateProducts($executionID);
|
|
if($diffProducts) $this->loadModel('action')->create('execution', $executionID, 'Managed', '', implode(',', array_keys($newProducts)));
|
|
}
|
|
}
|
|
|
|
/* Record multiple and waterfall project unlinked products. */
|
|
if($project->multiple and $project->model != 'waterfall' and $project->model != 'waterfallplus')
|
|
{
|
|
$this->dealUnlinkedProduct($oldProducts, $newProductIDs, $idList);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 记录多迭代及瀑布类项目移除的产品
|
|
* Record multiple and waterfall project unlinked products
|
|
*
|
|
* @param array $oldProducts
|
|
* @param array $newProductIDs
|
|
* @param array $idList
|
|
*
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function dealUnlinkedProduct(array $oldProducts, array $newProductIDs, array $idList): void
|
|
{
|
|
$oldExecutionProducts = $this->projectTao->getExecutionProductGroup($idList);
|
|
$unlinkedProducts = array_diff(array_keys($oldProducts), $newProductIDs);
|
|
if(!empty($unlinkedProducts))
|
|
{
|
|
$unlinkedProductPairs = array();
|
|
foreach($unlinkedProducts as $unlinkedProduct) $unlinkedProductPairs[$unlinkedProduct] = $oldProducts[$unlinkedProduct]->name;
|
|
|
|
$unlinkExecutions = array();
|
|
foreach($oldExecutionProducts as $executionID => $executionProducts)
|
|
{
|
|
$unlinkExecutionProducts = array_intersect_key($unlinkedProductPairs, $executionProducts);
|
|
if($unlinkExecutionProducts) $unlinkExecutions[$executionID] = $unlinkExecutionProducts;
|
|
}
|
|
|
|
foreach($unlinkExecutions as $executionID => $unlinkExecutionProducts) $this->loadModel('action')->create('execution', $executionID, 'unlinkproduct', '', implode(',', $unlinkExecutionProducts));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 提取关联用户故事无法移除的项目产品
|
|
* Extract related stories cannot be removed product.
|
|
*
|
|
* @param int $projectID
|
|
* @param object $project
|
|
*
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function extractUnModifyForm(int $projectID, object $project): void
|
|
{
|
|
$linkedBranches = array();
|
|
$linkedBranchIdList = array();
|
|
$branches = $this->project->getBranchesByProject($projectID);
|
|
$linkedProductIdList = empty($branches) ? '' : array_keys($branches);
|
|
$allProducts = $this->loadModel('program')->getProductPairs($project->parent, 'all', 'noclosed', $linkedProductIdList);
|
|
$linkedProducts = $this->loadModel('product')->getProducts($projectID, 'all', '', true, $linkedProductIdList);
|
|
$projectStories = $this->project->getStoriesByProject($projectID);
|
|
$projectBranches = $this->project->getBranchGroupByProject($projectID, array_keys($linkedProducts));
|
|
|
|
/* If the story of the product which linked the project,don't allow to remove the product. */
|
|
$unmodifiableProducts = array();
|
|
$unmodifiableBranches = array();
|
|
$unmodifiableMainBranches = array();
|
|
foreach($linkedProducts as $productID => $linkedProduct)
|
|
{
|
|
$linkedBranches[$productID] = array();
|
|
foreach($branches[$productID] as $branchID => $branch)
|
|
{
|
|
$linkedBranches[$productID][$branchID] = $branchID;
|
|
$linkedBranchIdList[$branchID] = $branchID;
|
|
|
|
if(!empty($projectStories[$productID][$branchID]) or !empty($projectBranches[$productID][$branchID]))
|
|
{
|
|
if($branchID == BRANCH_MAIN) $unmodifiableMainBranches[$productID] = $branchID;
|
|
array_push($unmodifiableProducts, $productID);
|
|
array_push($unmodifiableBranches, $branchID);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Build the product from other linked products. */
|
|
if($this->config->systemMode == 'ALM') $this->buildProductForm($project, $allProducts, $linkedBranchIdList, $linkedBranches, $linkedProducts);
|
|
|
|
$this->view->unmodifiableProducts = $unmodifiableProducts;
|
|
$this->view->unmodifiableBranches = $unmodifiableBranches;
|
|
$this->view->unmodifiableMainBranches = $unmodifiableMainBranches;
|
|
}
|
|
|
|
/**
|
|
* 初始化项目集下其他关联产品中当前产品
|
|
* Build the current product under the projectprogram.
|
|
*
|
|
* @param object $project
|
|
* @param array $allProducts
|
|
* @param array $linkedBranchIdList
|
|
* @param array $linkedBranches
|
|
* @param array $linkedProducts
|
|
*
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function buildProductForm(object $project, array $allProducts, array $linkedBranchIdList, array $linkedBranches, array $linkedProducts): void
|
|
{
|
|
$branchGroups = $this->loadModel('branch')->getByProducts(array_keys($allProducts), 'ignoreNormal|noclosed', $linkedBranchIdList);
|
|
$topProgramID = $project->parent ? $this->program->getTopByPath($project->path) : 0;
|
|
$productsGroupByProgram = $this->loadModel('product')->getProductsGroupByProgram();
|
|
|
|
$currentProducts = array();
|
|
foreach($productsGroupByProgram as $programID => $programProducts)
|
|
{
|
|
if($programID != $topProgramID)
|
|
{
|
|
$otherProducts = $this->getOtherProducts($programProducts, $branchGroups, $linkedBranches, $linkedProducts);
|
|
}
|
|
else
|
|
{
|
|
$currentProducts += $programProducts;
|
|
}
|
|
}
|
|
|
|
$this->view->currentProducts = $currentProducts;
|
|
$this->view->otherProducts = $otherProducts;
|
|
$this->view->branchGroups = $branchGroups;
|
|
$this->view->linkedBranches = $linkedBranches;
|
|
$this->view->linkedProducts = $linkedProducts;
|
|
$this->view->allProducts = $allProducts;
|
|
$this->view->allBranches = $this->loadModel('branch')->getByProducts(array_keys($allProducts), 'ignoreNormal');
|
|
}
|
|
|
|
/**
|
|
* 获取其他的关联产品
|
|
* Get other products under the projectprogram.
|
|
*
|
|
* @param object $programProducts
|
|
* @param array $branchGroups
|
|
* @param array $linkedBranches
|
|
* @param array $linkedProducts
|
|
*
|
|
* @access protected
|
|
* @return array
|
|
*/
|
|
protected function getOtherProducts(object $programProducts, array $branchGroups, array $linkedBranches, array $linkedProducts): array
|
|
{
|
|
$otherProducts = array();
|
|
foreach($programProducts as $productID => $productName)
|
|
{
|
|
if(!empty($branchGroups[$productID]))
|
|
{
|
|
foreach($branchGroups[$productID] as $branchID => $branchName)
|
|
{
|
|
if(isset($linkedProducts[$productID]) and isset($linkedBranches[$productID][$branchID])) continue;
|
|
$otherProducts["{$productID}_{$branchID}"] = $productName . '_' . $branchName;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(isset($linkedProducts[$productID])) continue;
|
|
$otherProducts[$productID] = $productName;
|
|
}
|
|
}
|
|
|
|
return $otherProducts;
|
|
}
|
|
|
|
/**
|
|
* 根据当前所在模块更新二级菜单
|
|
* set project menu
|
|
*
|
|
* @param int $projectID
|
|
* @param object $project
|
|
*
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function setProjectMenu(int $projectID, object $project): void
|
|
{
|
|
if($this->app->tab == 'program')
|
|
{
|
|
$this->loadModel('program')->setMenu($project->parent);
|
|
}
|
|
elseif($this->app->tab == 'project')
|
|
{
|
|
$this->project->setMenu($projectID);
|
|
}
|
|
}
|
|
}
|