Merge branch 'feature/projecttpl2.0' into release/21.7.3

This commit is contained in:
wangyuting
2025-07-03 09:31:33 +08:00
19 changed files with 105 additions and 23 deletions
+2
View File
@@ -0,0 +1,2 @@
ALTER TABLE `zt_project` ADD `tplAcl` char(30) NOT NULL DEFAULT 'open' AFTER `whitelist`;
ALTER TABLE `zt_project` ADD `tplWhiteList` text NULL AFTER `tplAcl`;
+2
View File
@@ -1615,6 +1615,8 @@ CREATE TABLE IF NOT EXISTS `zt_project` (
`team` varchar(90) NOT NULL DEFAULT '',
`acl` char(30) NOT NULL DEFAULT 'open',
`whitelist` text NULL,
`tplAcl` char(30) NOT NULL DEFAULT 'open',
`tplWhiteList` text NULL,
`order` mediumint(8) unsigned NOT NULL DEFAULT '0',
`vision` varchar(10) NOT NULL DEFAULT 'rnd',
`stageBy` enum('project','product') NOT NULL DEFAULT 'product',
+10
View File
@@ -534,6 +534,16 @@ class control extends baseControl
$uiID = $this->loadModel('workflowlayout')->getUIByData($flow->module, $action->action, $object);
$fieldList = $this->workflowaction->getPageFields($flow->module, $action->action, true, null, $uiID, $groupID);
/* 复制项目时显示被复制项目的工作流字段值。*/
if($moduleName == 'project' && $methodName == 'create')
{
foreach($fieldList as $field)
{
$objectValue = zget($object, $field->field, '');
if($objectValue) $field->default = $objectValue;
}
}
return $this->loadModel('flow')->buildFormFields($fields, $fieldList, array(), $object);
}
+1
View File
@@ -225,6 +225,7 @@ class dao extends baseDAO
$method = $this->app->getMethodName();
if($module == 'story' && $this->app->rawModule == 'requirement') $module = 'requirement';
if($module == 'story' && $this->app->rawModule == 'epic') $module = 'epic';
if($module == 'project' && $method == 'createtemplate') $method = 'create';
$flowAction = $this->dbh->query("SELECT * FROM " . TABLE_WORKFLOWACTION . " WHERE `module` = '{$module}' AND `action` = '{$method}' AND `buildin` = '1' AND `extensionType` = 'extend' AND `vision` = '{$this->config->vision}'")->fetch(PDO::FETCH_OBJ);
if(!$flowAction) return $this;
+4
View File
@@ -119,6 +119,10 @@ class form extends fixer
if($moduleName == 'projectrelease') $moduleName = 'release';
if($moduleName == 'projectbuild') $moduleName = 'build';
/* 项目复制用项目创建的工作流。 */
if($moduleName == 'project' && $methodName == 'copyconfirm') $methodName = 'create';
if($moduleName == 'project' && $methodName == 'edittemplate') $methodName = 'edit';
/* 用户需求和业务需求用自己的工作流。*/
if($moduleName == 'story' && $app->rawModule == 'requirement') $moduleName = 'requirement';
if($moduleName == 'story' && $app->rawModule == 'epic') $moduleName = 'epic';
+1
View File
@@ -3,3 +3,4 @@
#pick-pop-admin-menu .dropmenu-item + .dropmenu-item {margin-top: 4px}
#pick-pop-admin-menu .dropmenu-item.active {background-color: unset;}
#pick-pop-admin-menu .dropmenu-item:hover {color: rgba(var(--color-primary-500-rgb),var(--tw-text-opacity)); background-color: rgba(var(--color-primary-50-rgb),var(--tw-bg-opacity));}
.dropmenu-item .item-content .label {flex-shrink: 0;}
+5 -1
View File
@@ -77,12 +77,13 @@ class formPanel extends panel
{
global $app;
$moduleName = $app->getModuleName();
$methodName = $app->getMethodName();
if($moduleName == 'caselib') return data('lib');
if($moduleName == 'flow') return data('data');
if($moduleName == 'productplan') return data('plan');
if($moduleName == 'projectrelease') return data('release');
if($moduleName == 'projectbuild') return data('build');
if($moduleName == 'project' && $app->getMethodName() == 'create') return data('copyProject');
if($moduleName == 'project' && ($methodName == 'create' or $methodName == 'createtemplate')) return data('copyProject') ? data('copyProject') : null;
return data($moduleName);
}
@@ -105,6 +106,9 @@ class formPanel extends panel
}
}
if($moduleName == 'project' && $methodName == 'createtemplate') $methodName = 'create';
if($moduleName == 'project' && $methodName == 'edittemplate') $methodName = 'edit';
/* 反馈转化。 */
if($moduleName == 'feedback')
{
+6
View File
@@ -211,6 +211,12 @@ class datatable extends control
if(!$execution->hasProduct) unset($cols['branch']);
}
if($module == 'project' && $method == 'execution')
{
$project = $this->datatable->fetchByID($this->session->project, 'project');
if(!empty($project->isTpl)) unset($cols['deliverable']);
}
if($extra == 'unsetStory' && isset($cols['story'])) unset($cols['story']);
if($this->config->edition == 'ipd' && $module == 'product' && $method == 'browse' && $extra == 'story') unset($cols['roadmap']);
+11 -5
View File
@@ -367,6 +367,7 @@ class doc extends control
public function createLib(string $type = '', int $objectID = 0, int $libID = 0)
{
$this->app->loadLang('api');
$this->doc->setMenuByType($type, (int)$objectID, (int)$libID);
if(!empty($_POST))
{
@@ -386,10 +387,10 @@ class doc extends control
}
$objects = array();
if($type == 'project' && $this->app->tab == 'doc')
if($type == 'project')
{
$this->view->executionPairs = $this->execution->getPairs($objectID, 'all', 'multiple,leaf,noprefix');
$this->view->project = $this->project->getById($objectID);
$this->view->project = $this->project->getById($objectID);
if($this->app->tab == 'doc') $this->view->executionPairs = $this->execution->getPairs($objectID, 'all', 'multiple,leaf,noprefix');
}
if($type == 'execution')
@@ -397,6 +398,7 @@ class doc extends control
$objects = $this->execution->getPairs(0, 'all', 'multiple,leaf,noprefix,withobject');
$execution = $this->execution->getByID($objectID);
if($execution->type == 'stage') $this->lang->doc->execution = str_replace($this->lang->executionCommon, $this->lang->project->stage, $this->lang->doc->execution);
$this->view->execution = $this->doc->fetchByID($objectID, 'execution');
}
if($type == 'custom' || $type == 'mine' || $type == 'doctemplate')
@@ -456,6 +458,11 @@ class doc extends control
{
if(!common::hasPriv('doc', 'editSpace') && !common::hasPriv('doc', 'editLib')) return;
$lib = $this->doc->getLibByID($libID);
if($lib->type != 'project') $this->docZen->setAclForEditLib($lib);
$this->doc->setMenuByType($lib->type, (int)zget($lib, $lib->type, 0), (int)$libID);
if($lib->type == 'project') $this->docZen->setAclForEditLib($lib);
if(!empty($_POST))
{
$this->lang->doc->name = $this->lang->nameAB;
@@ -499,8 +506,6 @@ class doc extends control
$this->view->object = $execution;
}
$this->docZen->setAclForEditLib($lib);
$this->view->lib = $lib;
$this->view->groups = $this->loadModel('group')->getPairs();
$this->view->users = $this->user->getPairs('noletter|noclosed', $lib->users);
@@ -2208,6 +2213,7 @@ class doc extends control
$this->doc->setMenuByType($type, $spaceID, $libID);
$objectKey = $type . 'ID';
$this->view->$objectKey = $spaceID;
$this->view->$type = $this->doc->fetchByID($spaceID, $type);
}
if($type == 'mine') $menuType = 'my';
+10 -9
View File
@@ -25,26 +25,27 @@ if($type === 'project')
* 定义文档界面上的权限。
* Define the privs of doc app.
*/
$hasCustomSpace = $type == 'mine' || $type == 'custom';
$hasCustomSpace = $type == 'mine' || $type == 'custom';
$projectTemplate = ($type === 'project' && !empty($project->isTpl)) || ($type == 'execution' && !empty($execution->isTpl));
$privs = array();
$privs['create'] = hasPriv('doc', 'create');
$privs['edit'] = hasPriv('doc', 'edit');
$privs['delete'] = hasPriv('doc', 'delete');
$privs['create'] = hasPriv('doc', 'create') && !$projectTemplate;
$privs['edit'] = hasPriv('doc', 'edit') && !$projectTemplate;
$privs['delete'] = hasPriv('doc', 'delete') && !$projectTemplate;
$privs['view'] = hasPriv('doc', 'view');
$privs['effort'] = $this->config->edition != 'open' && hasPriv('effort', 'createForObject');
$privs['exportDoc'] = $this->config->edition != 'open' && hasPriv('doc', $type . '2export');
$privs['exportDoc'] = $this->config->edition != 'open' && hasPriv('doc', $type . '2export') && !$projectTemplate;
$privs['exportApi'] = $this->config->edition != 'open' && hasPriv('api', 'export');
$privs['moveDoc'] = hasPriv('doc', 'moveDoc');
$privs['moveDoc'] = hasPriv('doc', 'moveDoc') && !$projectTemplate;
$privs['collect'] = hasPriv('doc', 'collect');
$privs['createLib'] = hasPriv('doc', 'createLib');
$privs['editLib'] = hasPriv('doc', 'editLib');
$privs['moveLib'] = hasPriv('doc', 'moveLib');
$privs['moveLib'] = hasPriv('doc', 'moveLib') && !$projectTemplate;
$privs['deleteLib'] = hasPriv('doc', 'deleteLib');
$privs['sortDocLib'] = hasPriv('doc', 'sortDocLib');
$privs['uploadFile'] = hasPriv('doc', 'create');
$privs['uploadFile'] = hasPriv('doc', 'create') && !$projectTemplate;
$privs['editFile'] = hasPriv('file', 'edit');
$privs['deleteFile'] = hasPriv('doc', 'deleteFile');
$privs['exportFiles'] = hasPriv('doc', 'exportFiles');
$privs['exportFiles'] = hasPriv('doc', 'exportFiles') && !$projectTemplate;
$privs['createSpace'] = $hasCustomSpace && hasPriv('doc', 'createSpace');
$privs['deleteSpace'] = $hasCustomSpace && hasPriv('doc', 'deleteSpace');
$privs['editSpace'] = $hasCustomSpace && hasPriv('doc', 'editSpace');
+1
View File
@@ -26,6 +26,7 @@ $renderObjectBox = function(string $type) use ($lang, $objects, $objectID)
return formRow(
setClass('objectBox'),
formGroup(
set::id('object'),
set::width('5/6'),
set::label($lang->doc->{$type}),
set::name($type),
+1 -1
View File
@@ -2738,7 +2738,7 @@ class execution extends control
$executionGroups = $this->dao->select('*')->from(TABLE_EXECUTION) /* 按照项目分组,获取有权限访问的执行列表。*/
->where('deleted')->eq('0')
->andWhere('multiple')->eq('1')
->andWhere('type')->in('sprint,stage,kanban')
->andWhere('type')->in(!empty($execution->isTpl) ? 'sprint,stage' : 'sprint,stage,kanban')
->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->sprints)->fi()
->beginIF(!empty($execution->isTpl))->andWhere('isTpl')->eq('1')->fi()
->andWhere('project')->in(array_keys($projects))
+17 -1
View File
@@ -153,7 +153,6 @@ class executionModel extends model
unset($this->lang->execution->menu->burn);
unset($this->lang->execution->menu->kanban);
unset($this->lang->execution->menu->view);
unset($this->lang->execution->menu->story);
unset($this->lang->execution->menu->qa);
unset($this->lang->execution->menu->devops);
@@ -162,6 +161,22 @@ class executionModel extends model
unset($this->lang->execution->menu->effort);
unset($this->lang->execution->menu->more);
if(!empty($this->lang->execution->menu->view['subMenu']->gantt))
{
$this->lang->execution->menu->gantt = $this->lang->execution->menu->view['subMenu']->gantt;
$taskOrder = 0;
foreach($this->lang->execution->menuOrder as $order => $menu) if($menu == 'task') $taskOrder = $order;
$this->lang->execution->menuOrder[$taskOrder + 1] = 'gantt';
if(!empty($this->lang->project->menuOrder))
{
$taskOrder = 0;
foreach($this->lang->project->menuOrder as $order => $menu) if($menu == 'task') $taskOrder = $order;
$this->lang->project->menuOrder[$taskOrder + 1] = 'gantt';
}
}
if(!empty($this->lang->execution->menu->other['dropMenu']->pssp))
{
$this->lang->execution->menu->pssp = $this->lang->execution->menu->other['dropMenu']->pssp;
@@ -181,6 +196,7 @@ class executionModel extends model
}
}
unset($this->lang->execution->menu->view);
unset($this->lang->execution->menu->other);
if(isset($this->lang->execution->menu->settings['subMenu']->products)) unset($this->lang->execution->menu->settings['subMenu']->products); // 模板下隐藏产品
if(isset($this->lang->execution->menu->settings['subMenu']->whitelist)) unset($this->lang->execution->menu->settings['subMenu']->whitelist); // 模板下隐藏白名单
+7 -3
View File
@@ -1074,9 +1074,13 @@ $config->group->package->projectTemplate = new stdclass();
$config->group->package->projectTemplate->order = 5;
$config->group->package->projectTemplate->subset = 'projectTemplate';
$config->group->package->projectTemplate->privs = array();
$config->group->package->projectTemplate->privs['project-template'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 5, 'depend' => array('project-index'));
$config->group->package->projectTemplate->privs['project-createTemplate'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 10, 'depend' => array('project-index', 'project-template'));
$config->group->package->projectTemplate->privs['project-deleteTemplate'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 15, 'depend' => array('project-index', 'project-template'));
$config->group->package->projectTemplate->privs['project-template'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 5, 'depend' => array('project-index'));
$config->group->package->projectTemplate->privs['project-createTemplate'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 10, 'depend' => array('project-index', 'project-template'));
$config->group->package->projectTemplate->privs['project-editTemplate'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 15, 'depend' => array('project-index', 'project-template'));
$config->group->package->projectTemplate->privs['project-publishTemplate'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 20, 'depend' => array('project-index', 'project-template'));
$config->group->package->projectTemplate->privs['project-disableTemplate'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 25, 'depend' => array('project-index', 'project-template'));
$config->group->package->projectTemplate->privs['project-templatePriv'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 30, 'depend' => array('project-index', 'project-template'));
$config->group->package->projectTemplate->privs['project-deleteTemplate'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 35, 'depend' => array('project-index', 'project-template'));
$config->group->package->browseExecution = new stdclass();
$config->group->package->browseExecution->order = 5;
+3
View File
@@ -189,6 +189,9 @@ class projectModel extends model
$action = strtolower($action);
if($action == 'publishtemplate') return $project->status == 'wait' || $project->status == 'closed';
if($action == 'disabletemplate') return $project->status == 'doing';
if($action == 'close') return $project->status != 'closed';
if($action == 'group') return $project->model != 'kanban';
if($action == 'start') return $project->status == 'wait' || $project->status == 'suspended';
+15 -3
View File
@@ -9,7 +9,7 @@ $fields = defineFieldList('project');
$model = data('model');
$hasCode = !empty($config->setCode);
$currency = data('parentProgram') ? data('parentProgram.budgetUnit') : $config->project->defaultCurrency;
$disableStageBy = !empty(data('executions')) || data('app.rawMethod') == 'edit' ? true : false;
$disableStageBy = !empty(data('executions')) || data('app.rawMethod') == 'edit' || !empty(data('copyProject'));
$fields->field('parent')
->control('picker', array('required' => true))
@@ -46,6 +46,18 @@ $fields->field('PM')->control('picker')->items(data('PMUsers'));
unset($lang->project->endList[999]);
$isLongTime = data('project.end') == LONG_TIME;
$copyProject = data('copyProject');
if($copyProject)
{
$beginValue = $copyProject->begin;
$endValue = $copyProject->end == LONG_TIME ? '' : $copyProject->end;
}
else
{
$beginValue = data('project.begin') ? data('project.begin') : date('Y-m-d');
$endValue = data('project.end') == LONG_TIME ? '' : data('project.end');
}
$fields->field('begin')
->label($lang->project->planDate)
->required()
@@ -54,10 +66,10 @@ $fields->field('begin')
->control('dateRangePicker')
->beginName('begin')
->beginPlaceholder($lang->project->begin)
->beginValue(data('project.begin') ? data('project.begin') : date('Y-m-d'))
->beginValue($beginValue)
->endName('end')
->endPlaceholder($lang->project->end)
->endValue(data('project.end') == LONG_TIME ? '' : data('project.end'))
->endValue($endValue)
->endDisabled($isLongTime)
->endList($lang->project->endList)
->itemEnd()
+3
View File
@@ -47,10 +47,13 @@ $fields->field('days')
->control('input', array('className' => $copyProject ? 'has-warning' : ''))
->className($copyProject ? 'has-warning' : '')
->tip($copyProject ? $lang->project->copyProject->daysTips : null)
->value($copyProject ? data('copyProject.days') : '')
->tipClass($copyProject ? 'text-warning' : null);
$fields->field('productsBox')->hidden(data('copyProject') && data('copyProject.hasProduct') == 0);
$fields->field('PM')->value(data('copyProject.PM'));
$fields->field('budget')->value(data('copyProject.budget'));
$fields->field('budget')->foldable();
+2
View File
@@ -60,6 +60,8 @@ if($canBatchAction)
$fieldList = $config->project->execution->dtable->fieldList;
$fieldList['status']['statusMap']['changed'] = $lang->task->storyChange;
if(!empty($project->isTpl)) unset($fieldList['deliverable']);
/* waterfall & waterfallplus & ipd model with different edit link. */
if(in_array($project->model, array('waterfall', 'waterfallplus', 'ipd')))
{
@@ -110,6 +110,10 @@ foreach($projects as $programID => $programProjects)
{
$link = $indexLink;
}
elseif(!empty($project->isTpl))
{
$link = helper::createLink('project', 'execution', "status=undone&projectID={$project->id}");
}
else
{
$link = $defaultLink;