diff --git a/api/v1/entries/testresults.php b/api/v1/entries/testresults.php index a915d33511..cc570e834b 100644 --- a/api/v1/entries/testresults.php +++ b/api/v1/entries/testresults.php @@ -84,7 +84,7 @@ class testresultsEntry extends entry $reals[$stepID] = $step->real; } $this->setPost('result', $results); - $this->setPost('reals', $reals); + $this->setPost('real', $reals); } $control->runCase($runID, $caseID, $version); diff --git a/config/zentaopms.php b/config/zentaopms.php index 7f6d012c62..97041891c9 100644 --- a/config/zentaopms.php +++ b/config/zentaopms.php @@ -429,6 +429,7 @@ define('TABLE_ACL', '`' . $config->db->prefix . 'acl`'); define('TABLE_DESIGN', '`' . $config->db->prefix . 'design`'); define('TABLE_DESIGNSPEC', '`' . $config->db->prefix . 'designspec`'); +define('TABLE_DELIVERABLE', '`' . $config->db->prefix . 'deliverable`'); define('TABLE_DOCLIB', '`' . $config->db->prefix . 'doclib`'); define('TABLE_DOC', '`' . $config->db->prefix . 'doc`'); define('TABLE_DOCBLOCK', '`' . $config->db->prefix . 'docblock`'); @@ -672,6 +673,7 @@ $config->objectTables['demand'] = TABLE_DEMAND; $config->objectTables['demandpool'] = TABLE_DEMANDPOOL; $config->objectTables['demandspec'] = TABLE_DEMANDSPEC; $config->objectTables['demandreview'] = TABLE_DEMANDREVIEW; +$config->objectTables['deliverable'] = TABLE_DELIVERABLE; $config->objectTables['todo'] = TABLE_TODO; $config->objectTables['custom'] = TABLE_LANG; $config->objectTables['branch'] = TABLE_BRANCH; diff --git a/db/update21.6.1.sql b/db/update21.6.1.sql index ea92b0ec26..169db5f37a 100644 --- a/db/update21.6.1.sql +++ b/db/update21.6.1.sql @@ -4,4 +4,4 @@ ALTER TABLE `zt_storyspec` ADD COLUMN `docs` text NULL AFTER `files`; ALTER TABLE `zt_storyspec` ADD COLUMN `docVersions` text NULL AFTER `docs`; ALTER TABLE `zt_design` ADD COLUMN `docs` text NULL AFTER `storyVersion`; ALTER TABLE `zt_design` ADD COLUMN `docVersions` text NULL AFTER `docs`; -ALTER TABLE `zt_project` ADD `taskDateLimit` varchar(30) NOT NULL DEFAULT 'auto' AFTER `linkType`; +ALTER TABLE `zt_project` ADD `taskDateLimit` varchar(30) NOT NULL DEFAULT 'auto' AFTER `linkType`; \ No newline at end of file diff --git a/db/update21.6.sql b/db/update21.6.sql deleted file mode 100644 index ce5506e56a..0000000000 --- a/db/update21.6.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE `zt_relationoftasks` ADD COLUMN `project` mediumint(8) unsigned NOT NULL DEFAULT 0 AFTER `id`; -ALTER TABLE `zt_relationoftasks` MODIFY `execution` char(30) NOT NULL DEFAULT '' AFTER `project`; -UPDATE `zt_relationoftasks` SET `project` = (SELECT `project` FROM `zt_task` WHERE `id` = `zt_relationoftasks`.`task`); diff --git a/db/update21.7.sql b/db/update21.7.sql index 8daa59c65f..6805002597 100644 --- a/db/update21.7.sql +++ b/db/update21.7.sql @@ -13,4 +13,27 @@ UPDATE `zt_workflowlayout` SET `module` = 'build', `action` = 'browse' WHERE `mo UPDATE `zt_workflowaction` SET `module` = 'task', `action` = 'browse' WHERE `module` = 'execution' AND `action` = 'task'; UPDATE `zt_workflowlayout` SET `module` = 'task', `action` = 'browse' WHERE `module` = 'execution' AND `action` = 'task'; -ALTER TABLE `zt_workflowgroup` ADD `objectID` mediumint(8) unsigned NOT NULL DEFAULT '0' AFTER `id`; \ No newline at end of file +ALTER TABLE `zt_relationoftasks` ADD COLUMN `project` mediumint(8) unsigned NOT NULL DEFAULT 0 AFTER `id`; +ALTER TABLE `zt_relationoftasks` MODIFY `execution` char(30) NOT NULL DEFAULT '' AFTER `project`; +UPDATE `zt_relationoftasks` SET `project` = (SELECT `project` FROM `zt_task` WHERE `id` = `zt_relationoftasks`.`task`); + +CREATE TABLE IF NOT EXISTS `zt_deliverable` ( + `id` int(8) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `module` varchar(30) NULL, + `method` varchar(30) NULL, + `model` text NULL, + `type` enum('doc','file') NULL DEFAULT 'file', + `desc` text NULL, + `files` varchar(255) NULL, + `createdBy` varchar(30) NULL, + `createdDate` date NULL, + `lastEditedBy` varchar(30) NULL, + `lastEditedDate` date NULL, + `deleted` enum('0','1') NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +ALTER TABLE `zt_workflowgroup` ADD `objectID` mediumint(8) unsigned NOT NULL DEFAULT '0' AFTER `id`; +ALTER TABLE `zt_workflowgroup` ADD COLUMN `deliverable` text NULL AFTER `editedDate`; +ALTER TABLE `zt_project` ADD COLUMN `deliverable` text NULL AFTER `maxColWidth`; \ No newline at end of file diff --git a/db/zentao.sql b/db/zentao.sql index 466c885102..a52b03ba99 100755 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -578,6 +578,24 @@ CREATE TABLE IF NOT EXISTS `zt_screen` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +-- DROP TABLE IF EXISTS `zt_deliverable`; +CREATE TABLE IF NOT EXISTS `zt_deliverable` ( + `id` int(8) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `module` varchar(30) NULL, + `method` varchar(30) NULL, + `model` text NULL, + `type` enum('doc','file') NULL DEFAULT 'file', + `desc` text NULL, + `files` varchar(255) NULL, + `createdBy` varchar(30) NULL, + `createdDate` date NULL, + `lastEditedBy` varchar(30) NULL, + `lastEditedDate` date NULL, + `deleted` enum('0','1') NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + -- DROP TABLE IF EXISTS `zt_dimension`; CREATE TABLE IF NOT EXISTS `zt_dimension` ( `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, @@ -1596,6 +1614,7 @@ CREATE TABLE IF NOT EXISTS `zt_project` ( `colWidth` smallint(6) NOT NULL DEFAULT '264', `minColWidth` smallint(6) NOT NULL DEFAULT '200', `maxColWidth` smallint(6) NOT NULL DEFAULT '384', + `deliverable` text NULL, `deleted` enum('0','1') NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; @@ -13250,6 +13269,7 @@ CREATE TABLE IF NOT EXISTS `zt_workflowgroup` ( `createdDate` datetime NULL, `editedBy` varchar(30) NOT NULL DEFAULT '', `editedDate` datetime NULL, + `deliverable` text NULL, `deleted` enum('0', '1') NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/lib/zin/func.php b/lib/zin/func.php index 469d0979a9..e651f5237a 100644 --- a/lib/zin/func.php +++ b/lib/zin/func.php @@ -249,6 +249,7 @@ function iconPicker(): iconPicker {return createWg('iconPicker', func_get_args() function relatedObjectList(): relatedObjectList {return createWg('relatedObjectList', func_get_args());} function taskAssignedTo(): taskAssignedTo {return createWg('taskAssignedTo', func_get_args());} function docList(): docList {return createWg('docList', func_get_args());} +function deliverable(): deliverable {return createWg('deliverable', func_get_args());} if(is_dir(__DIR__ . DS . 'wg' . DS . 'schedule')) { @@ -258,4 +259,4 @@ if(is_dir(__DIR__ . DS . 'wg' . DS . 'schedule')) if(is_dir(__DIR__ . DS . 'wg' . DS . 'boardeditor')) { function boardEditor(): boardEditor {return createWg('boardeditor', func_get_args());} -} +} \ No newline at end of file diff --git a/lib/zin/wg/backbtn/v1.php b/lib/zin/wg/backbtn/v1.php index 5b20fca7ac..0f67b0e291 100644 --- a/lib/zin/wg/backbtn/v1.php +++ b/lib/zin/wg/backbtn/v1.php @@ -96,7 +96,9 @@ class backBtn extends btn 'approvalflow' => 'approvalflow-browse', 'host' => 'host-browse,my-index', 'deploy' => 'deploy-browse', - 'program' => 'program-browse,program-productview' + 'program' => 'program-browse,program-productview', + 'workflowgroup' => 'workflowgroup-project,workflowgroup-deliverable', + 'deliverable' => 'deliverable-browse' ); $props = parent::getProps(); diff --git a/lib/zin/wg/deliverable/js/v1.js b/lib/zin/wg/deliverable/js/v1.js new file mode 100644 index 0000000000..80fa48a10f --- /dev/null +++ b/lib/zin/wg/deliverable/js/v1.js @@ -0,0 +1,44 @@ +/** + * 获取附件的操作按钮。 + * Get file actions. + */ +window.getDeliverableFileActions = function(file, deliverable) +{ + let actions = []; + if(canDownload && typeof file.id === 'number') actions[0] = {icon: 'download', key: 'download', url: $.createLink('file', 'download', 'id=' + file.id), target: '_blank'}; + actions[1] = {icon: 'edit', key: 'rename'}; + actions[2] = {icon: 'trash', key: 'delete'}; + + /* 可以预览的文件。 */ + if(['txt', 'jpg', 'jpeg', 'gif', 'png', 'bmp', 'mp4'].includes(file.extension) && canDownload) + { + actions[3] = {icon: 'eye', key: 'view', url: $.createLink('file', 'download', `fileID=${file.id}&mouse=left`), 'data-toggle' : 'modal', 'data-size' : 'lg'}; + } + + return actions; +} + +/** + * 获取文档的操作按钮。 + * Get file actions. + */ +window.getDocActions = function(doc, deliverable) +{ + let actions = []; + actions[0] = {icon: 'eye', key: 'view', url: $.createLink('doc', 'view', 'docID=' + doc.id)}; + actions[1] = {icon: 'edit', key: 'rename'}; + actions[2] = {icon: 'trash', key: 'delete'}; + return actions; +} + +/** + * 获取交付物的操作按钮。 + * Get deliverable actions. + */ +window.getDeliverableActions = function(deliverable) +{ + let actions = []; + actions[0] = {text: addFile, icon: 'file', key: 'selectFile'}; + if(deliverable.template > 0 && canDownload) actions[1] = {text: downloadTemplate, icon: 'download', key: 'downloadTemplate', url: $.createLink('file', 'download', 'templateID=' + deliverable.template), target: '_blank'}; + return actions; +} diff --git a/lib/zin/wg/deliverable/v1.php b/lib/zin/wg/deliverable/v1.php new file mode 100644 index 0000000000..dc3c7c86f9 --- /dev/null +++ b/lib/zin/wg/deliverable/v1.php @@ -0,0 +1,78 @@ +loadLang('doc'); + $app->loadLang('file'); + jsVar('addFile', $lang->doc->addFile); + jsVar('downloadTemplate', $lang->doc->downloadTemplate); + jsVar('deleteItem', $lang->delete); + jsVar('canDownload', hasPriv('file', 'download')); + + return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js'); + } + + /** + * 构建组件。 + * Build. + * + * @access protected + * @return zui + */ + protected function build(): zui + { + global $lang, $app; + $app->loadLang('doc'); + $app->loadLang('file'); + + $formName = $this->prop('formName') ? $this->prop('formName') : 'deliverable'; + + if(!$this->hasProp('maxFileSize')) + { + $maxFileSize = ini_get('upload_max_filesize'); + $lastChar = substr($maxFileSize, -1); + $fileSizeUnit = array('K', 'M', 'G', 'T'); + if(in_array($lastChar, $fileSizeUnit)) $maxFileSize .= 'B'; + $this->setProp('maxFileSize', $maxFileSize); + } + + return zui::deliverableList + ( + set::formName($formName), + set::items($this->prop('items')), + set::docPicker(array('placeholder' => $lang->doc->selectDoc, 'items' => helper::createLink('doc', 'ajaxGetMineDocs', 'keyword={search}'))), + set::getFileActions(jsRaw('window.getDeliverableFileActions')), + set::getDocActions(jsRaw('window.getDocActions')), + set::getEmptyActions(jsRaw('window.getDeliverableActions')), + set::maxFileSize($this->prop('maxFileSize')), + set::extraCategory($lang->other) + ); + } +} diff --git a/lib/zin/wg/dropmenu/css/v1.css b/lib/zin/wg/dropmenu/css/v1.css index a1308d5d1a..9904ddbb1d 100644 --- a/lib/zin/wg/dropmenu/css/v1.css +++ b/lib/zin/wg/dropmenu/css/v1.css @@ -1,4 +1,4 @@ -#pick-pop-admin-menu {width: 139px!important;} +#pick-pop-admin-menu {width: 145px!important;} #pick-pop-admin-menu .dropmenu-list {padding: 8px;} #pick-pop-admin-menu .dropmenu-item + .dropmenu-item {margin-top: 4px} #pick-pop-admin-menu .dropmenu-item.active {background-color: unset;} diff --git a/module/action/config.php b/module/action/config.php index 9899981166..1c26be3a42 100755 --- a/module/action/config.php +++ b/module/action/config.php @@ -71,6 +71,7 @@ $config->action->objectNameFields['prompt'] = 'name'; $config->action->objectNameFields['miniprogram'] = 'name'; $config->action->objectNameFields['holiday'] = 'name'; $config->action->objectNameFields['system'] = 'name'; +$config->action->objectNameFields['deliverable'] = 'name'; $config->action->commonImgSize = 870; diff --git a/module/action/lang/de.php b/module/action/lang/de.php index 17418cb670..40f0be5363 100644 --- a/module/action/lang/de.php +++ b/module/action/lang/de.php @@ -191,6 +191,7 @@ $lang->action->objectTypes['board'] = 'Board'; $lang->action->objectTypes['boardspace'] = 'Board Space'; $lang->action->objectTypes['productline'] = 'Product Line'; $lang->action->objectTypes['system'] = $lang->product->system; +$lang->action->objectTypes['deliverable'] = 'Deliverable'; /* Used to describe operation history. */ $lang->action->desc = new stdclass(); @@ -937,6 +938,7 @@ $lang->action->label->instance = 'Application|instance|view|id=%s'; $lang->action->label->prompt = 'Prompt|ai|promptview|id=%s'; $lang->action->label->miniprogram = 'Mini Program|aiapp|browseminiprogram|id=%s'; $lang->action->label->holiday = 'Holiday|holiday|browse|'; +$lang->action->label->deliverable = 'Deliverable|deliverable|view|id=%s'; /* Object type. */ $lang->action->search = new stdclass(); diff --git a/module/action/lang/en.php b/module/action/lang/en.php index bc5d4af4ea..5860a6b5fd 100755 --- a/module/action/lang/en.php +++ b/module/action/lang/en.php @@ -191,6 +191,7 @@ $lang->action->objectTypes['board'] = 'Board'; $lang->action->objectTypes['boardspace'] = 'Board Space'; $lang->action->objectTypes['productline'] = 'Product Line'; $lang->action->objectTypes['system'] = $lang->product->system; +$lang->action->objectTypes['deliverable'] = 'Deliverable'; /* Used to describe operation history. */ $lang->action->desc = new stdclass(); @@ -937,6 +938,7 @@ $lang->action->label->instance = 'Application|instance|view|id=%s'; $lang->action->label->prompt = 'Prompt|ai|promptview|id=%s'; $lang->action->label->miniprogram = 'Mini Program|aiapp|browseminiprogram|id=%s'; $lang->action->label->holiday = 'Holiday|holiday|browse|'; +$lang->action->label->deliverable = 'Deliverable|deliverable|view|id=%s'; /* Object type. */ $lang->action->search = new stdclass(); diff --git a/module/action/lang/fr.php b/module/action/lang/fr.php index 5fce10c0f7..61ee3a5353 100644 --- a/module/action/lang/fr.php +++ b/module/action/lang/fr.php @@ -191,6 +191,7 @@ $lang->action->objectTypes['board'] = 'Board'; $lang->action->objectTypes['boardspace'] = 'Board Space'; $lang->action->objectTypes['productline'] = 'Product Line'; $lang->action->objectTypes['system'] = $lang->product->system; +$lang->action->objectTypes['deliverable'] = 'Deliverable'; /* Used to describe operation history. */ $lang->action->desc = new stdclass(); @@ -937,6 +938,7 @@ $lang->action->label->instance = 'Application|instance|view|id=%s'; $lang->action->label->prompt = 'Prompt|ai|promptview|id=%s'; $lang->action->label->miniprogram = 'Mini Program|aiapp|browseminiprogram|id=%s'; $lang->action->label->holiday = 'Holiday|holiday|browse|'; +$lang->action->label->deliverable = 'Deliverable|deliverable|view|id=%s'; /* Object type. */ $lang->action->search = new stdclass(); diff --git a/module/action/lang/zh-cn.php b/module/action/lang/zh-cn.php index 6d33955720..2a0de805e2 100755 --- a/module/action/lang/zh-cn.php +++ b/module/action/lang/zh-cn.php @@ -191,6 +191,7 @@ $lang->action->objectTypes['board'] = '白板'; $lang->action->objectTypes['boardspace'] = '白板空间'; $lang->action->objectTypes['productline'] = '产品线'; $lang->action->objectTypes['system'] = $lang->product->system; +$lang->action->objectTypes['deliverable'] = '交付物'; /* 用来描述操作历史记录。*/ $lang->action->desc = new stdclass(); @@ -937,6 +938,7 @@ $lang->action->label->instance = '服务|instance|view|id=%s'; $lang->action->label->prompt = '提词|ai|promptview|id=%s'; $lang->action->label->miniprogram = '小程序|aiapp|browseminiprogram|id=%s'; $lang->action->label->holiday = '节假日|holiday|browse|'; +$lang->action->label->deliverable = '交付物|deliverable|view|id=%s'; /* Object type. */ $lang->action->search = new stdclass(); diff --git a/module/action/model.php b/module/action/model.php index 4c0b54675b..701f70d153 100755 --- a/module/action/model.php +++ b/module/action/model.php @@ -940,6 +940,12 @@ class actionModel extends model foreach($actions as $action) { $item = new stdClass(); + if(strlen(trim(($action->comment))) !== 0) + { + $item->comment = $this->formatActionComment($action->comment); + $currentUserCanEdit = $action->id == $endAction->id && $action->actor == $account && $action->action == 'commented'; + $item->commentEditable = $commentEditable && common::hasPriv('action', 'editComment') && $currentUserCanEdit; + } if($action->action === 'assigned' || $action->action === 'toaudit') { @@ -951,7 +957,7 @@ class actionModel extends model $action->actor = zget($users, $action->actor); if(str_contains($action->actor, ':')) $action->actor = substr($action->actor, strpos($action->actor, ':') + 1); - if(!empty($action->history)) $item->historyChanges = $this->renderChanges($action->objectType, $action->history); + if(!empty($action->history)) $item->historyChanges = $this->renderChanges($action->objectType, $action->objectID, $action->history); $item->id = $action->id; $item->action = $action->action; @@ -1452,12 +1458,13 @@ class actionModel extends model * Render histories of every action. * * @param string $objectType + * @param int $objectID * @param array $histories * @param bool $canChangeTag * @access public * @return string */ - public function renderChanges(string $objectType, array $histories, bool $canChangeTag = true): string + public function renderChanges(string $objectType, int $objectID, array $histories, bool $canChangeTag = true): string { if(empty($histories)) return ''; @@ -1507,7 +1514,14 @@ class actionModel extends model } else { - $content .= sprintf($this->lang->action->desc->diff1, $history->fieldLabel, $history->old, $history->new); + if($history->field == 'deliverable' && ($objectType == 'project' || $objectType == 'execution')) + { + $content .= $this->processDeliverableJson($objectType, $objectID, $history); + } + else + { + $content .= sprintf($this->lang->action->desc->diff1, $history->fieldLabel, $history->old, $history->new); + } } } return $content; @@ -1518,14 +1532,15 @@ class actionModel extends model * Print histories of every action. * * @param string $objectType + * @param int $objectID * @param array $histories * @param bool $canChangeTag * @access public * @return void */ - public function printChanges(string $objectType, array $histories, bool $canChangeTag = true): void + public function printChanges(string $objectType, int $objectID, array $histories, bool $canChangeTag = true): void { - $content = $this->renderChanges($objectType, $histories, $canChangeTag); + $content = $this->renderChanges($objectType, $objectID, $histories, $canChangeTag); if(is_string($content)) echo $content; } diff --git a/module/admin/config.php b/module/admin/config.php index 48b6d8c167..f205582b85 100755 --- a/module/admin/config.php +++ b/module/admin/config.php @@ -8,7 +8,7 @@ if(!isset($config->safe->weak)) $config->safe->weak = '123456,password,12345,123 $config->admin->menuGroup['system'] = array('custom|mode', 'backup', 'cron', 'action|trash', 'admin|xuanxuan', 'setting|xuanxuan', 'admin|license', 'admin|checkweak', 'admin|resetpwdsetting', 'admin|safe', 'cache|setting', 'custom|timezone', 'search|buildindex', 'admin|tableengine', 'ldap', 'custom|libreoffice', 'conference', 'watermark', 'client', 'system|browsebackup', 'system|restorebackup'); $config->admin->menuGroup['company'] = array('dept', 'company', 'user', 'group', 'tutorial'); $config->admin->menuGroup['switch'] = array('admin|setmodule'); -$config->admin->menuGroup['model'] = array('auditcl', 'stage', 'design', 'cmcl', 'reviewcl', 'custom|required', 'custom|set', 'custom|flow', 'custom|code', 'custom|percent','custom|estimate', 'custom|hours', 'subject', 'process', 'activity', 'zoutput', 'classify', 'holiday', 'reviewsetting', 'custom|project'); +$config->admin->menuGroup['model'] = array('auditcl', 'stage', 'deliverable', 'design', 'cmcl', 'reviewcl', 'custom|required', 'custom|set', 'custom|flow', 'custom|code', 'custom|percent','custom|estimate', 'custom|hours', 'subject', 'process', 'activity', 'zoutput', 'classify', 'holiday', 'reviewsetting', 'custom|project'); $config->admin->menuGroup['feature'] = array('custom|set', 'custom|product', 'custom|execution', 'custom|required', 'custom|kanban', 'measurement', 'meetingroom', 'custom|browsestoryconcept', 'custom|kanban', 'sqlbuilder', 'report', 'custom|limittaskdate', 'measurement'); $config->admin->menuGroup['template'] = array('custom|set', 'baseline'); $config->admin->menuGroup['message'] = array('mail', 'webhook', 'sms', 'message'); diff --git a/module/admin/lang/de.php b/module/admin/lang/de.php index f46edad4ef..11bc8a98f8 100755 --- a/module/admin/lang/de.php +++ b/module/admin/lang/de.php @@ -118,6 +118,7 @@ $lang->admin->setModule->opportunitylib = 'Opportunity Lib'; $lang->admin->setModule->practicelib = 'Practice Lib'; $lang->admin->setModule->componentlib = 'Component Lib'; $lang->admin->setModule->devops = 'DevOps'; +$lang->admin->setModule->deliverable = 'Deliverable'; $lang->admin->setModule->kanban = 'Kanban'; $lang->admin->setModule->OA = 'OA'; $lang->admin->setModule->deploy = 'Deploy'; diff --git a/module/admin/lang/en.php b/module/admin/lang/en.php index ca0b92b227..aec0983c5b 100755 --- a/module/admin/lang/en.php +++ b/module/admin/lang/en.php @@ -118,6 +118,7 @@ $lang->admin->setModule->opportunitylib = 'Opportunity Lib'; $lang->admin->setModule->practicelib = 'Practice Lib'; $lang->admin->setModule->componentlib = 'Component Lib'; $lang->admin->setModule->devops = 'DevOps'; +$lang->admin->setModule->deliverable = 'Deliverable'; $lang->admin->setModule->kanban = 'Kanban'; $lang->admin->setModule->OA = 'OA'; $lang->admin->setModule->deploy = 'Deploy'; diff --git a/module/admin/lang/fr.php b/module/admin/lang/fr.php index 1d5b91d323..38b0490e3a 100755 --- a/module/admin/lang/fr.php +++ b/module/admin/lang/fr.php @@ -118,6 +118,7 @@ $lang->admin->setModule->opportunitylib = 'Opportunity Lib'; $lang->admin->setModule->practicelib = 'Practice Lib'; $lang->admin->setModule->componentlib = 'Component Lib'; $lang->admin->setModule->devops = 'DevOps'; +$lang->admin->setModule->deliverable = 'Deliverable'; $lang->admin->setModule->kanban = 'Kanban'; $lang->admin->setModule->OA = 'OA'; $lang->admin->setModule->deploy = 'Deploy'; diff --git a/module/admin/lang/menu.php b/module/admin/lang/menu.php index cef8ec25a5..8acf9d1353 100644 --- a/module/admin/lang/menu.php +++ b/module/admin/lang/menu.php @@ -74,7 +74,7 @@ $lang->admin->menuList->company['menuOrder']['5'] = 'dept'; $lang->admin->menuList->company['menuOrder']['10'] = 'browseUser'; $lang->admin->menuList->company['menuOrder']['15'] = 'group'; -$lang->admin->menuList->model['subMenu']['common'] = array('link' => "{$lang->globalSetting}|custom|required|module=project", 'subModule' => 'custom,subject,holiday,stage', 'exclude' => 'stage-browse,stage-plusbrowse,stage-create,stage-edit,stage-batchcreate'); +$lang->admin->menuList->model['subMenu']['common'] = array('link' => "{$lang->globalSetting}|custom|required|module=project", 'subModule' => 'custom,subject,holiday,stage,deliverable', 'exclude' => 'stage-browse,stage-plusbrowse,stage-create,stage-edit,stage-batchcreate'); $lang->admin->menuList->model['subMenu']['scrum'] = array('link' => "{$lang->scrumModel}|auditcl|scrumbrowse|", 'subModule' => 'auditcl'); $lang->admin->menuList->model['subMenu']['waterfall'] = array('link' => "{$lang->waterfallModel}|stage|browse|", 'subModule' => 'stage', 'exclude' => 'stage-settype,stage-plusbrowse'); $lang->admin->menuList->model['subMenu']['agileplus'] = array('link' => "{$lang->agilePlusModel}|auditcl|agileplusbrowse|", 'subModule' => 'auditcl'); @@ -86,13 +86,14 @@ $lang->admin->menuList->model['menuOrder']['15'] = 'waterfall'; $lang->admin->menuList->model['menuOrder']['20'] = 'agileplus'; $lang->admin->menuList->model['menuOrder']['25'] = 'waterfallplus'; -$lang->admin->menuList->model['tabMenu']['common']['project'] = array('link' => "{$lang->project->common}|custom|required|module=project", 'alias' => 'set,project', 'exclude' => 'custom-required', 'links' => array('custom|set|module=project&field=unitList')); -if(helper::hasFeature('waterfall') or helper::hasFeature('waterfallplus')) $lang->admin->menuList->model['tabMenu']['common']['stage'] = array('link' => "{$lang->stage->type}|stage|settype|", 'subModule' => 'stage'); -$lang->admin->menuList->model['tabMenu']['common']['build'] = array('link' => "{$lang->build->common}|custom|required|module=build", 'alias' => 'set', 'exclude' => 'custom'); -$lang->admin->menuList->model['tabMenu']['common']['flow'] = array('link' => "{$lang->custom->flow}|custom|flow|", 'divider' => true); +$lang->admin->menuList->model['tabMenu']['common']['project'] = array('link' => "{$lang->project->common}|custom|required|module=project", 'alias' => 'set,project', 'exclude' => 'custom-required', 'links' => array('custom|set|module=project&field=unitList')); +if(helper::hasFeature('waterfall') || helper::hasFeature('waterfallplus')) $lang->admin->menuList->model['tabMenu']['common']['stage'] = array('link' => "{$lang->stage->type}|stage|settype|", 'subModule' => 'stage'); +if(helper::hasFeature('deliverable') && ($config->edition == 'max' || $config->edition == 'ipd')) $lang->admin->menuList->model['tabMenu']['common']['deliverable'] = array('link' => "{$lang->deliverable->common}|deliverable|browse|", 'subModule' => 'deliverable'); +$lang->admin->menuList->model['tabMenu']['common']['build'] = array('link' => "{$lang->build->common}|custom|required|module=build", 'alias' => 'set', 'exclude' => 'custom'); +$lang->admin->menuList->model['tabMenu']['common']['flow'] = array('link' => "{$lang->custom->flow}|custom|flow|", 'divider' => true); -$lang->admin->menuList->model['tabMenu']['common']['percent'] = array('link' => "{$lang->stage->percent}|custom|percent|"); -$lang->admin->menuList->model['tabMenu']['common']['hours'] = array('link' => "{$lang->workingHour}|custom|hours|", 'subModule' => 'holiday', 'links' => array('holiday|browse|', 'custom|hours|')); +$lang->admin->menuList->model['tabMenu']['common']['percent'] = array('link' => "{$lang->stage->percent}|custom|percent|"); +$lang->admin->menuList->model['tabMenu']['common']['hours'] = array('link' => "{$lang->workingHour}|custom|hours|", 'subModule' => 'holiday', 'links' => array('holiday|browse|', 'custom|hours|')); if(helper::hasFeature('waterfall')) $lang->admin->menuList->model['tabMenu']['waterfall']['stage'] = array('link' => "{$lang->stage->list}|stage|browse|", 'subModule' => 'stage', 'exclude' => 'stage-plusbrowse'); if(helper::hasFeature('waterfallplus')) $lang->admin->menuList->model['tabMenu']['waterfallplus']['stage'] = array('link' => "{$lang->stage->list}|stage|plusbrowse|", 'subModule' => 'stage', 'exclude' => 'stage-browse'); $lang->admin->menuList->model['tabMenu']['menuOrder']['common']['5'] = 'project'; @@ -103,6 +104,10 @@ $lang->admin->menuList->model['tabMenu']['menuOrder']['common']['40'] = 'p $lang->admin->menuList->model['tabMenu']['menuOrder']['common']['45'] = 'hours'; if(helper::hasFeature('waterfall')) $lang->admin->menuList->model['tabMenu']['menuOrder']['waterfall']['5'] = 'stage'; if(helper::hasFeature('waterfallplus')) $lang->admin->menuList->model['tabMenu']['menuOrder']['waterfallplus']['5'] = 'stage'; +if($config->edition == 'max' or $config->edition == 'ipd') +{ + $lang->admin->menuList->model['tabMenu']['menuOrder']['common']['15'] = 'deliverable'; +} $lang->admin->menuList->feature['subMenu']['my'] = array('link' => "{$lang->my->common}|custom|set|module=todo&field=priList", 'exclude' => 'set,required'); $lang->admin->menuList->feature['subMenu']['product'] = array('link' => "{$lang->productCommon}|custom|required|module=product", 'exclude' => 'set,required', 'alias' => 'browsestoryconcept,product'); diff --git a/module/admin/lang/zh-cn.php b/module/admin/lang/zh-cn.php index 2be5c58512..18dbf40abe 100755 --- a/module/admin/lang/zh-cn.php +++ b/module/admin/lang/zh-cn.php @@ -118,6 +118,7 @@ $lang->admin->setModule->opportunitylib = '机会库'; $lang->admin->setModule->practicelib = '最佳实践库'; $lang->admin->setModule->componentlib = '组件库'; $lang->admin->setModule->devops = 'DevOps'; +$lang->admin->setModule->deliverable = '交付物'; $lang->admin->setModule->kanban = '通用看板'; $lang->admin->setModule->OA = '办公'; $lang->admin->setModule->deploy = '运维'; @@ -170,7 +171,7 @@ $lang->admin->menuSetting['user']['name'] = '人员管理'; $lang->admin->menuSetting['user']['desc'] = '维护部门、添加人员、分组配置权限。'; $lang->admin->menuSetting['switch']['name'] = '功能开关'; $lang->admin->menuSetting['switch']['desc'] = '打开、关闭系统部分功能。'; -$lang->admin->menuSetting['model']['name'] = '模型配置'; +$lang->admin->menuSetting['model']['name'] = '项目模型配置'; $lang->admin->menuSetting['model']['desc'] = '不同项目管理模型和项目通用要素配置。'; $lang->admin->menuSetting['feature']['name'] = '功能配置'; $lang->admin->menuSetting['feature']['desc'] = '按照功能菜单进行系统的要素配置。'; diff --git a/module/common/lang/common.php b/module/common/lang/common.php index 01677fd34a..1c242060ad 100644 --- a/module/common/lang/common.php +++ b/module/common/lang/common.php @@ -105,8 +105,12 @@ $lang->ops = new stdclass(); $lang->domain = new stdclass(); $lang->service = new stdclass(); $lang->deployment = new stdclass(); +$lang->deliverable = new stdclass(); $lang->metric = new stdclass(); +$lang->projectDeliverable = new stdclass(); +$lang->executionDeliverable = new stdclass(); + $lang->ai = new stdclass(); $lang->aiapp = new stdclass(); diff --git a/module/common/lang/de.php b/module/common/lang/de.php index 46ff6aea75..c46dcca65c 100644 --- a/module/common/lang/de.php +++ b/module/common/lang/de.php @@ -289,37 +289,40 @@ $lang->aiapp->common = 'AI'; $lang->product->system = 'Application'; $lang->configure->common = 'Configure'; -$lang->programstakeholder->common = 'Stakeholder'; -$lang->featureswitch->common = 'Features On/Off'; -$lang->importdata->common = 'Import data'; -$lang->systemsetting->common = 'System setting'; -$lang->staffmanage->common = 'User management'; -$lang->modelconfig->common = 'Pattern setting'; -$lang->featureconfig->common = 'Features config'; -$lang->doctemplate->common = 'Doc template'; -$lang->notifysetting->common = 'Notification'; -$lang->bidesign->common = 'BI design'; -$lang->personalsettings->common = 'Personal setting '; -$lang->projectsettings->common = 'Setting'; -$lang->dataaccess->common = 'Data permission'; -$lang->executiongantt->common = 'Gantt chart'; -$lang->executionkanban->common = 'Kanban'; -$lang->executionburn->common = 'Burndown chart'; -$lang->executioncfd->common = 'Cumulative Flow Diagram'; -$lang->executionstory->common = 'Story'; -$lang->executionqa->common = 'QA'; -$lang->executionsettings->common = 'Setting'; -$lang->generalcomment->common = 'Comment'; -$lang->generalping->common = 'Timeout prevention'; -$lang->generaltemplate->common = 'Template'; -$lang->generaleffort->common = 'General log'; -$lang->productsettings->common = 'Product setting'; -$lang->projectreview->common = 'Review'; -$lang->projecttrack->common = 'Matrix'; -$lang->projectqa->common = 'QA'; -$lang->holidayseason->common = 'Holiday'; -$lang->codereview->common = 'Review'; -$lang->repocode->common = 'Code'; +$lang->programstakeholder->common = 'Stakeholder'; +$lang->featureswitch->common = 'Features On/Off'; +$lang->importdata->common = 'Import data'; +$lang->systemsetting->common = 'System setting'; +$lang->staffmanage->common = 'User management'; +$lang->modelconfig->common = 'Pattern setting'; +$lang->featureconfig->common = 'Features config'; +$lang->doctemplate->common = 'Doc template'; +$lang->notifysetting->common = 'Notification'; +$lang->bidesign->common = 'BI design'; +$lang->personalsettings->common = 'Personal setting '; +$lang->projectsettings->common = 'Setting'; +$lang->dataaccess->common = 'Data permission'; +$lang->executiongantt->common = 'Gantt chart'; +$lang->executionkanban->common = 'Kanban'; +$lang->executionburn->common = 'Burndown chart'; +$lang->executioncfd->common = 'Cumulative Flow Diagram'; +$lang->executionstory->common = 'Story'; +$lang->executionqa->common = 'QA'; +$lang->executionsettings->common = 'Setting'; +$lang->generalcomment->common = 'Comment'; +$lang->generalping->common = 'Timeout prevention'; +$lang->generaltemplate->common = 'Template'; +$lang->generaleffort->common = 'General log'; +$lang->productsettings->common = 'Product setting'; +$lang->projectreview->common = 'Review'; +$lang->projecttrack->common = 'Matrix'; +$lang->projectqa->common = 'QA'; +$lang->holidayseason->common = 'Holiday'; +$lang->codereview->common = 'Review'; +$lang->repocode->common = 'Code'; +$lang->deliverable->common = 'Deliverable'; +$lang->projectDeliverable->common = 'Project Deliverable'; +$lang->executionDeliverable->common = 'Execution Deliverable'; $lang->personnel->common = 'Member'; $lang->personnel->invest = 'Investment'; diff --git a/module/common/lang/en.php b/module/common/lang/en.php index d15451b81a..2008ab8507 100644 --- a/module/common/lang/en.php +++ b/module/common/lang/en.php @@ -289,37 +289,40 @@ $lang->aiapp->common = 'AI'; $lang->product->system = 'Application'; $lang->configure->common = 'Configure'; -$lang->programstakeholder->common = 'Stakeholder'; -$lang->featureswitch->common = 'Features On/Off'; -$lang->importdata->common = 'Import data'; -$lang->systemsetting->common = 'System setting'; -$lang->staffmanage->common = 'User management'; -$lang->modelconfig->common = 'Pattern setting'; -$lang->featureconfig->common = 'Features config'; -$lang->doctemplate->common = 'Doc template'; -$lang->notifysetting->common = 'Notification'; -$lang->bidesign->common = 'BI design'; -$lang->personalsettings->common = 'Personal setting '; -$lang->projectsettings->common = 'Setting'; -$lang->dataaccess->common = 'Data permission'; -$lang->executiongantt->common = 'Gantt chart'; -$lang->executionkanban->common = 'Kanban'; -$lang->executionburn->common = 'Burndown chart'; -$lang->executioncfd->common = 'Cumulative Flow Diagram'; -$lang->executionstory->common = 'Story'; -$lang->executionqa->common = 'QA'; -$lang->executionsettings->common = 'Setting'; -$lang->generalcomment->common = 'Comment'; -$lang->generalping->common = 'Timeout prevention'; -$lang->generaltemplate->common = 'Template'; -$lang->generaleffort->common = 'General log'; -$lang->productsettings->common = 'Product setting'; -$lang->projectreview->common = 'Review'; -$lang->projecttrack->common = 'Matrix'; -$lang->projectqa->common = 'QA'; -$lang->holidayseason->common = 'Holiday'; -$lang->codereview->common = 'Review'; -$lang->repocode->common = 'Code'; +$lang->programstakeholder->common = 'Stakeholder'; +$lang->featureswitch->common = 'Features On/Off'; +$lang->importdata->common = 'Import data'; +$lang->systemsetting->common = 'System setting'; +$lang->staffmanage->common = 'User management'; +$lang->modelconfig->common = 'Pattern setting'; +$lang->featureconfig->common = 'Features config'; +$lang->doctemplate->common = 'Doc template'; +$lang->notifysetting->common = 'Notification'; +$lang->bidesign->common = 'BI design'; +$lang->personalsettings->common = 'Personal setting '; +$lang->projectsettings->common = 'Setting'; +$lang->dataaccess->common = 'Data permission'; +$lang->executiongantt->common = 'Gantt chart'; +$lang->executionkanban->common = 'Kanban'; +$lang->executionburn->common = 'Burndown chart'; +$lang->executioncfd->common = 'Cumulative Flow Diagram'; +$lang->executionstory->common = 'Story'; +$lang->executionqa->common = 'QA'; +$lang->executionsettings->common = 'Setting'; +$lang->generalcomment->common = 'Comment'; +$lang->generalping->common = 'Timeout prevention'; +$lang->generaltemplate->common = 'Template'; +$lang->generaleffort->common = 'General log'; +$lang->productsettings->common = 'Product setting'; +$lang->projectreview->common = 'Review'; +$lang->projecttrack->common = 'Matrix'; +$lang->projectqa->common = 'QA'; +$lang->holidayseason->common = 'Holiday'; +$lang->codereview->common = 'Review'; +$lang->repocode->common = 'Code'; +$lang->deliverable->common = 'Deliverable'; +$lang->projectDeliverable->common = 'Project Deliverable'; +$lang->executionDeliverable->common = 'Execution Deliverable'; $lang->personnel->common = 'Member'; $lang->personnel->invest = 'Investment'; diff --git a/module/common/lang/fr.php b/module/common/lang/fr.php index 1a85fd6b83..fe0936db33 100644 --- a/module/common/lang/fr.php +++ b/module/common/lang/fr.php @@ -289,37 +289,40 @@ $lang->aiapp->common = 'AI'; $lang->product->system = 'Application'; $lang->configure->common = 'Configure'; -$lang->programstakeholder->common = 'Stakeholder'; -$lang->featureswitch->common = 'Features On/Off'; -$lang->importdata->common = 'Import data'; -$lang->systemsetting->common = 'System setting'; -$lang->staffmanage->common = 'User management'; -$lang->modelconfig->common = 'Pattern setting'; -$lang->featureconfig->common = 'Features config'; -$lang->doctemplate->common = 'Doc template'; -$lang->notifysetting->common = 'Notification'; -$lang->bidesign->common = 'BI design'; -$lang->personalsettings->common = 'Personal setting '; -$lang->projectsettings->common = 'Setting'; -$lang->dataaccess->common = 'Data permission'; -$lang->executiongantt->common = 'Gantt chart'; -$lang->executionkanban->common = 'Kanban'; -$lang->executionburn->common = 'Burndown chart'; -$lang->executioncfd->common = 'Cumulative Flow Diagram'; -$lang->executionstory->common = 'Story'; -$lang->executionqa->common = 'QA'; -$lang->executionsettings->common = 'Setting'; -$lang->generalcomment->common = 'Comment'; -$lang->generalping->common = 'Timeout prevention'; -$lang->generaltemplate->common = 'Template'; -$lang->generaleffort->common = 'General log'; -$lang->productsettings->common = 'Product setting'; -$lang->projectreview->common = 'Review'; -$lang->projecttrack->common = 'Matrix'; -$lang->projectqa->common = 'QA'; -$lang->holidayseason->common = 'Holiday'; -$lang->codereview->common = 'Review'; -$lang->repocode->common = 'Code'; +$lang->programstakeholder->common = 'Stakeholder'; +$lang->featureswitch->common = 'Features On/Off'; +$lang->importdata->common = 'Import data'; +$lang->systemsetting->common = 'System setting'; +$lang->staffmanage->common = 'User management'; +$lang->modelconfig->common = 'Pattern setting'; +$lang->featureconfig->common = 'Features config'; +$lang->doctemplate->common = 'Doc template'; +$lang->notifysetting->common = 'Notification'; +$lang->bidesign->common = 'BI design'; +$lang->personalsettings->common = 'Personal setting '; +$lang->projectsettings->common = 'Setting'; +$lang->dataaccess->common = 'Data permission'; +$lang->executiongantt->common = 'Gantt chart'; +$lang->executionkanban->common = 'Kanban'; +$lang->executionburn->common = 'Burndown chart'; +$lang->executioncfd->common = 'Cumulative Flow Diagram'; +$lang->executionstory->common = 'Story'; +$lang->executionqa->common = 'QA'; +$lang->executionsettings->common = 'Setting'; +$lang->generalcomment->common = 'Comment'; +$lang->generalping->common = 'Timeout prevention'; +$lang->generaltemplate->common = 'Template'; +$lang->generaleffort->common = 'General log'; +$lang->productsettings->common = 'Product setting'; +$lang->projectreview->common = 'Review'; +$lang->projecttrack->common = 'Matrix'; +$lang->projectqa->common = 'QA'; +$lang->holidayseason->common = 'Holiday'; +$lang->codereview->common = 'Review'; +$lang->repocode->common = 'Code'; +$lang->deliverable->common = 'Deliverable'; +$lang->projectDeliverable->common = 'Project Deliverable'; +$lang->executionDeliverable->common = 'Execution Deliverable'; $lang->personnel->common = 'Member'; $lang->personnel->invest = 'Investment'; diff --git a/module/common/lang/menu.php b/module/common/lang/menu.php index 4d96f5ed33..fd3161b489 100644 --- a/module/common/lang/menu.php +++ b/module/common/lang/menu.php @@ -783,6 +783,7 @@ $lang->navGroup->extension = 'admin'; $lang->navGroup->action = 'admin'; $lang->navGroup->convert = 'admin'; $lang->navGroup->stage = 'admin'; +$lang->navGroup->deliverable = 'admin'; $lang->navGroup->featureswitch = 'admin'; $lang->navGroup->importdata = 'admin'; $lang->navGroup->systemsetting = 'admin'; diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php index 22bd919f27..c6b088a140 100644 --- a/module/common/lang/zh-cn.php +++ b/module/common/lang/zh-cn.php @@ -289,37 +289,40 @@ $lang->aiapp->common = 'AI'; $lang->product->system = '应用'; $lang->configure->common = '配置'; -$lang->programstakeholder->common = '干系人'; -$lang->featureswitch->common = '功能开关'; -$lang->importdata->common = '数据导入'; -$lang->systemsetting->common = '系统设置'; -$lang->staffmanage->common = '人员管理'; -$lang->modelconfig->common = '模型配置'; -$lang->featureconfig->common = '功能配置'; -$lang->doctemplate->common = '文档模板'; -$lang->notifysetting->common = '通知设置'; -$lang->bidesign->common = 'BI设计'; -$lang->personalsettings->common = '个人设置'; -$lang->projectsettings->common = '设置'; -$lang->dataaccess->common = '数据权限'; -$lang->executiongantt->common = '甘特图'; -$lang->executionkanban->common = '看板'; -$lang->executionburn->common = '燃尽图'; -$lang->executioncfd->common = '累积流图'; -$lang->executionstory->common = '研发需求'; -$lang->executionqa->common = '测试'; -$lang->executionsettings->common = '设置'; -$lang->generalcomment->common = '备注'; -$lang->generalping->common = '防超时'; -$lang->generaltemplate->common = '模板'; -$lang->generaleffort->common = '通用日志'; -$lang->productsettings->common = '产品设置'; -$lang->projectreview->common = '评审'; -$lang->projecttrack->common = '矩阵'; -$lang->projectqa->common = '测试'; -$lang->holidayseason->common = '节假日'; -$lang->codereview->common = '问题'; -$lang->repocode->common = '代码'; +$lang->programstakeholder->common = '干系人'; +$lang->featureswitch->common = '功能开关'; +$lang->importdata->common = '数据导入'; +$lang->systemsetting->common = '系统设置'; +$lang->staffmanage->common = '人员管理'; +$lang->modelconfig->common = '项目模型配置'; +$lang->featureconfig->common = '功能配置'; +$lang->doctemplate->common = '文档模板'; +$lang->notifysetting->common = '通知设置'; +$lang->bidesign->common = 'BI设计'; +$lang->personalsettings->common = '个人设置'; +$lang->projectsettings->common = '设置'; +$lang->dataaccess->common = '数据权限'; +$lang->executiongantt->common = '甘特图'; +$lang->executionkanban->common = '看板'; +$lang->executionburn->common = '燃尽图'; +$lang->executioncfd->common = '累积流图'; +$lang->executionstory->common = '研发需求'; +$lang->executionqa->common = '测试'; +$lang->executionsettings->common = '设置'; +$lang->generalcomment->common = '备注'; +$lang->generalping->common = '防超时'; +$lang->generaltemplate->common = '模板'; +$lang->generaleffort->common = '通用日志'; +$lang->productsettings->common = '产品设置'; +$lang->projectreview->common = '评审'; +$lang->projecttrack->common = '矩阵'; +$lang->projectqa->common = '测试'; +$lang->holidayseason->common = '节假日'; +$lang->codereview->common = '问题'; +$lang->repocode->common = '代码'; +$lang->deliverable->common = '交付物'; +$lang->projectDeliverable->common = '项目交付物'; +$lang->executionDeliverable->common = '执行交付物'; $lang->personnel->common = '人员'; $lang->personnel->invest = '投入人员'; diff --git a/module/common/view/action.html.php b/module/common/view/action.html.php index d1dadede7f..46c53b7416 100755 --- a/module/common/view/action.html.php +++ b/module/common/view/action.html.php @@ -61,7 +61,7 @@ history)):?>
- action->printChanges($action->objectType, $action->history);?> + action->printChanges($action->objectType, $action->objectID, $action->history);?>
comment))) != 0):?> diff --git a/module/common/view/mail.footer.html.php b/module/common/view/mail.footer.html.php index 3d8d365619..3f632ce7b5 100644 --- a/module/common/view/mail.footer.html.php +++ b/module/common/view/mail.footer.html.php @@ -28,7 +28,7 @@ if(file_exists($extViewFile)) history)):?> -
action->printChanges($action->objectType, $action->history, false);?>
+
action->printChanges($action->objectType, $action->objectID, $action->history, false);?>
diff --git a/module/doc/lang/de.php b/module/doc/lang/de.php index efa160a474..e8b1f5eed7 100644 --- a/module/doc/lang/de.php +++ b/module/doc/lang/de.php @@ -253,6 +253,8 @@ $lang->doc->uploadFormat = 'Upload Format'; $lang->doc->editedList = 'File editor'; $lang->doc->moveTo = 'Move to'; $lang->doc->notSupportExport = 'This document does not support export'; +$lang->doc->downloadTemplate = 'Download Template'; +$lang->doc->addFile = 'Add File'; $lang->doc->preview = 'Preview'; $lang->doc->insertTitle = 'Insert %s list'; @@ -439,6 +441,7 @@ $lang->doc->noCollectedDoc = 'Sie haben kein Dokument gesammelt.'; $lang->doc->errorEmptyLib = 'No data in document library.'; $lang->doc->confirmUpdateContent = 'You have a document that is not saved from last time. Do you want to continue editing it?'; $lang->doc->selectLibType = 'Please select a type of doc library.'; +$lang->doc->selectDoc = 'Please select a doc'; $lang->doc->noLibreOffice = 'You does not have access to office conversion settings!'; $lang->doc->errorParentChapter = 'The parent chapter cannot be its own chapter or sub chapter!'; $lang->doc->errorOthersCreated = 'There are documents created by others in this library. You cannot move it.'; diff --git a/module/doc/lang/en.php b/module/doc/lang/en.php index d8ee47e89f..a51c5e3105 100644 --- a/module/doc/lang/en.php +++ b/module/doc/lang/en.php @@ -253,6 +253,8 @@ $lang->doc->uploadFormat = 'Upload Format'; $lang->doc->editedList = 'File editor'; $lang->doc->moveTo = 'Move to'; $lang->doc->notSupportExport = 'This document does not support export'; +$lang->doc->downloadTemplate = 'Download Template'; +$lang->doc->addFile = 'Add File'; $lang->doc->preview = 'Preview'; $lang->doc->insertTitle = 'Insert %s list'; @@ -439,6 +441,7 @@ $lang->doc->noCollectedDoc = 'You have not favorited any documents. $lang->doc->errorEmptyLib = 'No data in document library.'; $lang->doc->confirmUpdateContent = 'You have a document that is not saved from last time. Do you want to continue editing it?'; $lang->doc->selectLibType = 'Please select a type of doc library.'; +$lang->doc->selectDoc = 'Please select a doc'; $lang->doc->noLibreOffice = 'You does not have access to office conversion settings!'; $lang->doc->errorParentChapter = 'The parent chapter cannot be its own chapter or sub chapter!'; $lang->doc->errorOthersCreated = 'There are documents created by others in this library. You cannot move it.'; diff --git a/module/doc/lang/fr.php b/module/doc/lang/fr.php index dd6f2b11ff..9668928712 100644 --- a/module/doc/lang/fr.php +++ b/module/doc/lang/fr.php @@ -253,6 +253,8 @@ $lang->doc->uploadFormat = 'Upload Format'; $lang->doc->editedList = 'File editor'; $lang->doc->moveTo = 'Move to'; $lang->doc->notSupportExport = 'This document does not support export'; +$lang->doc->downloadTemplate = 'Download Template'; +$lang->doc->addFile = 'Add File'; $lang->doc->preview = 'Preview'; $lang->doc->insertTitle = 'Insert %s list'; @@ -439,6 +441,7 @@ $lang->doc->noCollectedDoc = "Vous avez aucun document dans vos fav $lang->doc->errorEmptyLib = 'No data in document library.'; $lang->doc->confirmUpdateContent = 'You have a document that is not saved from last time. Do you want to continue editing it?'; $lang->doc->selectLibType = 'Please select a type of doc library.'; +$lang->doc->selectDoc = 'Please select a doc'; $lang->doc->noLibreOffice = 'You does not have access to office conversion settings!'; $lang->doc->errorParentChapter = 'The parent chapter cannot be its own chapter or sub chapter!'; $lang->doc->errorOthersCreated = 'There are documents created by others in this library. You cannot move it.'; diff --git a/module/doc/lang/zh-cn.php b/module/doc/lang/zh-cn.php index 1adcfed06d..99ab4dbb73 100644 --- a/module/doc/lang/zh-cn.php +++ b/module/doc/lang/zh-cn.php @@ -253,6 +253,8 @@ $lang->doc->uploadFormat = '上传格式'; $lang->doc->editedList = '文档编辑者'; $lang->doc->moveTo = '移动至'; $lang->doc->notSupportExport = '(此文档暂不支持导出)'; +$lang->doc->downloadTemplate = '下载模板'; +$lang->doc->addFile = '提交文件'; $lang->doc->preview = '预览'; $lang->doc->insertTitle = '插入%s列表'; @@ -439,6 +441,7 @@ $lang->doc->noCollectedDoc = '您还没有收藏任何文档。'; $lang->doc->errorEmptyLib = '文档库暂无数据。'; $lang->doc->confirmUpdateContent = '检查到您有未保存的文档内容,是否继续编辑?'; $lang->doc->selectLibType = '请选择文档库类型'; +$lang->doc->selectDoc = '请选择文档'; $lang->doc->noLibreOffice = '您还没有office转换设置访问权限!'; $lang->doc->errorParentChapter = '父章节不能是自身章节及子章节!'; $lang->doc->errorOthersCreated = '该库下其他人创建的文档暂不支持移动,是否确认移动?'; diff --git a/module/doc/model.php b/module/doc/model.php index ddc700cf49..72aa61c735 100644 --- a/module/doc/model.php +++ b/module/doc/model.php @@ -1571,12 +1571,12 @@ class docModel extends model } $files = $this->loadModel('file')->getUpload(); - if($doc->type == 'attachment' && (empty($files) || isset($files['name']))) return dao::$errors['files'] = sprintf($this->lang->error->notempty, $this->lang->doc->uploadFile); + if($doc->type == 'attachment' && empty($doc->copy) && (empty($files) || isset($files['name']))) return dao::$errors['files'] = sprintf($this->lang->error->notempty, $this->lang->doc->uploadFile); $doc->draft = $isDraft ? $docContent->content : ''; $doc->vision = $this->config->vision; $doc->version = $isDraft ? 0 : 1; - $this->dao->insert(TABLE_DOC)->data($doc, 'content')->autoCheck()->batchCheck($requiredFields, 'notempty')->exec(); + $this->dao->insert(TABLE_DOC)->data($doc, 'content,copy')->autoCheck()->batchCheck($requiredFields, 'notempty')->exec(); if(dao::isError()) return false; $docID = $this->dao->lastInsertID(); diff --git a/module/execution/config/dtable.php b/module/execution/config/dtable.php index 0fe823bf71..790d50be86 100644 --- a/module/execution/config/dtable.php +++ b/module/execution/config/dtable.php @@ -49,6 +49,17 @@ $config->execution->dtable->fieldList['status']['width'] = '80'; $config->execution->dtable->fieldList['status']['group'] = '1'; $config->execution->dtable->fieldList['status']['show'] = true; +if(in_array($config->edition, array('max', 'ipd')) && helper::hasFeature('deliverable')) +{ + $config->execution->dtable->fieldList['deliverable']['title'] = $lang->execution->deliverableAbbr; + $config->execution->dtable->fieldList['deliverable']['name'] = 'deliverable'; + $config->execution->dtable->fieldList['deliverable']['type'] = 'html'; + $config->execution->dtable->fieldList['deliverable']['width'] = '100px'; + $config->execution->dtable->fieldList['deliverable']['group'] = '1'; + $config->execution->dtable->fieldList['deliverable']['show'] = true; + $config->execution->dtable->fieldList['deliverable']['sortType'] = false; +} + $config->execution->dtable->fieldList['PM']['title'] = $lang->execution->execPM; $config->execution->dtable->fieldList['PM']['name'] = 'PM'; $config->execution->dtable->fieldList['PM']['type'] = 'avatarBtn'; diff --git a/module/execution/control.php b/module/execution/control.php index 2f13d2ae81..4efd825551 100644 --- a/module/execution/control.php +++ b/module/execution/control.php @@ -1362,7 +1362,7 @@ class execution extends control } $executionIDList = $this->post->executionIDList; - $executions = $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->in($executionIDList)->fetchAll('id'); + $executions = $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->in($executionIDList)->fetchAll('id', false); $relatedProjects = $this->dao->select('id,project')->from(TABLE_PROJECT)->where('id')->in($executionIDList)->fetchPairs(); /* 获取执行所属的项目列表。*/ $projects = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->in($relatedProjects)->fetchAll('id'); /* 获取执行所属的项目列表中每个项目的项目信息。*/ @@ -1415,8 +1415,8 @@ class execution extends control $executionIdList = $this->post->executionIDList; if(is_string($executionIdList)) $executionIdList = explode(',', $executionIdList); - $filteredStages = $this->execution->batchChangeStatus($executionIdList, $status); - if(!$filteredStages) return $this->sendSuccess(array('load' => true)); + $message = $this->execution->batchChangeStatus($executionIdList, $status); + if(empty($message['byChild']) && empty($message['byDeliverable'])) return $this->sendSuccess(array('load' => true)); $alertMsg = ''; if($status == 'wait') @@ -1426,18 +1426,28 @@ class execution extends control if(empty($project) or (!empty($project) and strpos($project->model, 'waterfall') !== false)) { $executionLang = (empty($project) or (!empty($project) and $project->model == 'waterfallplus')) ? $this->lang->execution->common : $this->lang->stage->common; - $alertMsg = sprintf($this->lang->execution->hasStartedTaskOrSubStage, $executionLang, $filteredStages); + $alertMsg = sprintf($this->lang->execution->hasStartedTaskOrSubStage, $executionLang, $message['byChild']); } if(!empty($project) and strpos('agileplus,scrum', $project->model) !== false) { $executionLang = $project->model == 'scrum' ? $this->lang->executionCommon : $this->lang->execution->common; - $alertMsg = sprintf($this->lang->execution->hasStartedTask, $executionLang, $filteredStages); + $alertMsg = sprintf($this->lang->execution->hasStartedTask, $executionLang, $message['byChild']); + } + } + if($status == 'suspended') $alertMsg = sprintf($this->lang->execution->hasSuspendedOrClosedChildren, $message['byChild']); + if($status == 'closed') + { + if(!empty($message['byChild'])) + { + $alertMsg .= sprintf($this->lang->execution->hasNotClosedChildren, $message['byChild']); + } + elseif(!empty($message['byDeliverable'])) + { + $alertMsg .= sprintf($this->lang->execution->cannotCloseByDeliverable, $message['byDeliverable']); } } - if($status == 'suspended') $alertMsg = sprintf($this->lang->execution->hasSuspendedOrClosedChildren, $filteredStages); - if($status == 'closed') $alertMsg = sprintf($this->lang->execution->hasNotClosedChildren, $filteredStages); - return $this->sendSuccess(array('message' => $alertMsg, 'load' => true)); + return $this->send(array('load' => array('alert' => $alertMsg), 'result' => 'success')); } /** @@ -1666,7 +1676,11 @@ class execution extends control if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); $project = $this->loadModel('project')->getById($execution->project); - if(in_array($project->model, array('waterfall', 'waterfallplus', 'ipd'))) $this->loadModel('programplan')->computeProgress($executionID, 'close'); + if(in_array($project->model, array('waterfall', 'waterfallplus', 'ipd'))) + { + $result = $this->loadModel('programplan')->computeProgress($executionID, 'close'); + if(is_array($result)) return $this->send($result); + } $this->executeHooks($executionID); @@ -2893,6 +2907,7 @@ class execution extends control $this->app->loadLang('stage'); $this->app->loadLang('programplan'); $this->loadModel('product'); + $this->loadModel('project'); $this->loadModel('datatable'); $from = $this->app->tab; diff --git a/module/execution/js/batchedit.ui.js b/module/execution/js/batchedit.ui.js index 9360903740..18c91de382 100644 --- a/module/execution/js/batchedit.ui.js +++ b/module/execution/js/batchedit.ui.js @@ -25,7 +25,16 @@ window.renderRowData = function($row, index, row) options.disabled = row.grade > 1 && parentType != 'mix'; if(projectModel == 'ipd') options.disabled = true; } + $row.attr('data-parent', row.parent); + + $row.find('[data-name="lifetime"]').find('.picker-box').on('inited', function(e, info) + { + let $attribute = info[0]; + $attribute.render({items: stageItems, required: true, name: 'attribute[' + row.id + ']', disabled: row.grade > 1 && parentType != 'mix'}); + if(typeof row != 'undefined' && typeof row.hasDeliverable != 'undefined') $attribute.render({disabled: true}); + $(e.target).attr('data-parent', row.parent); + }); } $row.find('[data-name="lifetime"]').find('.picker-box').on('inited', function(e, info) { info[0].render(options); }); diff --git a/module/execution/model.php b/module/execution/model.php index 623e070514..30242b3f4c 100755 --- a/module/execution/model.php +++ b/module/execution/model.php @@ -539,17 +539,24 @@ class executionModel extends model * @param array $executionIdList * @param string $status * @access public - * @return string 返回不符合条件被过滤了的执行,来提示执行下任务或子阶段已经开始,无法修改,已过滤。参见 story#41875。 + * @return array 返回不符合条件被过滤了的执行,来提示执行下任务或子阶段已经开始,无法修改,已过滤。参见 story#41875。 */ - public function batchChangeStatus(array $executionIdList, string $status): string + public function batchChangeStatus(array $executionIdList, string $status): array { /* Sort the IDs, the child stage comes first, and the parent stage follows. */ - $executionIdList = $this->dao->select('id')->from(TABLE_EXECUTION)->where('id')->in($executionIdList)->orderBy('grade_desc')->fetchPairs(); + $executionList = $this->dao->select('id,name,status,grade,deliverable')->from(TABLE_EXECUTION)->where('id')->in($executionIdList)->orderBy('grade_desc')->fetchAll('id'); $this->loadModel('programplan'); - $filteredStages = ''; - foreach($executionIdList as $executionID) + $message = array('byChild' => '', 'byDeliverable' => ''); + foreach($executionList as $executionID => $execution) { + $needCheckDeliverable = $status == 'closed' && $execution->status == 'doing' && $execution->grade == 1; + if(in_array($this->config->edition, array('max', 'ipd')) && $needCheckDeliverable && !$this->canCloseByDeliverable($execution)) + { + $message['byDeliverable'] .= '#' . $execution->id . ' ' . $execution->name . "\n"; + continue; + } + /* The state of the parent stage or the sibling stage may be affected by the child stage before the change, so it cannot be checked in advance. */ $selfAndChildrenList = $this->programplan->getSelfAndChildrenList($executionID); $selfAndChildren = $selfAndChildrenList[$executionID]; @@ -557,7 +564,7 @@ class executionModel extends model if($status == 'wait' and $execution->status != 'wait') { - $filteredStages .= $this->changeStatus2Wait($executionID, $selfAndChildren); + $message['byChild'] .= $this->changeStatus2Wait($executionID, $selfAndChildren); } if($status == 'doing' and $execution->status != 'doing') @@ -567,11 +574,12 @@ class executionModel extends model if(($status == 'suspended' and $execution->status != 'suspended') or ($status == 'closed' and $execution->status != 'closed')) { - $filteredStages .= $this->changeStatus2Inactive($executionID, $status, $selfAndChildren); + $message['byChild'] .= $this->changeStatus2Inactive($executionID, $status, $selfAndChildren); } } - return trim($filteredStages, ','); + $message['byChild'] = trim($message['byChild'], ','); + return $message; } /** @@ -906,9 +914,9 @@ class executionModel extends model * @param int $executionID * @param object $postData * @access public - * @return array|false + * @return int|false */ - public function close(int $executionID, object $postData): array|false + public function close(int $executionID, object $postData): int|false { $oldExecution = $this->fetchById($executionID); /* Save previous execution to variable for later compare. */ @@ -937,7 +945,7 @@ class executionModel extends model } $this->loadModel('score')->create('execution', 'close', $oldExecution); - return $changes; + return $actionID; } /** @@ -1452,7 +1460,7 @@ class executionModel extends model ->fi() ->orderBy($orderBy) ->page($pager, 't1.id') - ->fetchAll('id'); + ->fetchAll('id', false); } /** @@ -1481,6 +1489,7 @@ class executionModel extends model $today = helper::today(); $burns = $this->getBurnData($executions); $parentExecutions = $this->dao->select('parent,parent')->from(TABLE_EXECUTION)->where('parent')->ne(0)->andWhere('deleted')->eq(0)->fetchPairs(); + $statusGroup = $this->dao->select('parent,status')->from(TABLE_EXECUTION)->where('parent')->in(array_keys($executions))->fetchGroup('parent', 'status'); /* Get workingDays. */ $earliestEnd = $today; @@ -1503,6 +1512,13 @@ class executionModel extends model if(isset($parentExecutions[$execution->id])) $executions[$execution->id]->isParent = 1; if(empty($productID) && !empty($productList[$execution->id])) $execution->product = trim($productList[$execution->id]->product, ','); + /** 如果子执行都关闭了,则父执行可以手动关闭。 */ + if(isset($statusGroup[$execution->id])) + { + $childStatus = array_keys($statusGroup[$execution->id]); + if(count($childStatus) == 1 && $childStatus[0] == 'closed') $execution->parentCanClose = true; + } + /* Judge whether the execution is delayed. */ if($execution->status != 'done' && $execution->status != 'closed' && $execution->status != 'suspended' && !empty($workingDays)) { @@ -3827,7 +3843,7 @@ class executionModel extends model $action = strtolower($action); if($action == 'start') return $execution->status == 'wait'; - if($action == 'close') return $execution->status != 'closed' && (!isset($execution->isParent) || (isset($execution->isParent) && !$execution->isParent)); + if($action == 'close') return $execution->status != 'closed' && (empty($execution->isParent) || !empty($execution->parentCanClose)); if($action == 'suspend') return $execution->status == 'wait' || $execution->status == 'doing'; if($action == 'putoff') return $execution->status == 'wait' || $execution->status == 'doing'; if($action == 'activate') return $execution->status == 'suspended' || $execution->status == 'closed'; @@ -4797,6 +4813,7 @@ class executionModel extends model $execution->id = 'pid' . (string)$execution->id; $execution->projectID = $execution->project; $execution->project = $execution->projectName; + $execution->rawParent = $execution->parent; $execution->parent = (isset($executionList[$execution->parent]) && $execution->parent && $execution->grade > 1) ? 'pid' . (string)$execution->parent : ''; $execution->hasChild = !empty($execution->isParent); $execution->isParent = !empty($execution->isParent) or !empty($execution->tasks); @@ -4841,6 +4858,8 @@ class executionModel extends model $execution->PMAvatar = zget($avatarList, $execution->PMAccount, ''); } + if(in_array($this->config->edition, array('max', 'ipd'))) $execution->deliverable = $this->project->countDeliverable($execution, 'execution'); + $rows[$execution->id] = $execution; /* Append tasks and child stages. */ diff --git a/module/execution/tao.php b/module/execution/tao.php index 984290a0fd..071f91c29d 100644 --- a/module/execution/tao.php +++ b/module/execution/tao.php @@ -142,8 +142,8 @@ class executionTao extends executionModel $executions[$executionID]->lastEditedBy = $this->app->user->account; $executions[$executionID]->lastEditedDate = helper::now(); - if(isset($postData->code)) $executions[$executionID]->code = $executionCode; - if(isset($postData->project)) $executions[$executionID]->project = zget($postData->project, $executionID, 0); + if(isset($postData->code)) $executions[$executionID]->code = $executionCode; + if(isset($postData->project)) $executions[$executionID]->project = zget($postData->project, $executionID, 0); if(isset($postData->attribute[$executionID])) $executions[$executionID]->attribute = zget($postData->attribute, $executionID, ''); if(isset($postData->lifetime[$executionID])) $executions[$executionID]->lifetime = $postData->lifetime[$executionID]; diff --git a/module/execution/test/tao/setkanbanmenu.php b/module/execution/test/tao/setkanbanmenu.php index 782884b17c..d5499a2938 100644 --- a/module/execution/test/tao/setkanbanmenu.php +++ b/module/execution/test/tao/setkanbanmenu.php @@ -11,7 +11,17 @@ title=测试executionModel->setKanbanMenu(); timeout=0 cid=1 +- 测试替换看板执行的二级菜单第kanban条的link属性 @看板|execution|kanban|executionID=%s +- 查看交付物菜单第deliverable条的link属性 @交付物|execution|deliverable|executionID=%s +- 查看构建菜单第build条的link属性 @构建|execution|build|executionID=%s +- 查看累积流图菜单第CFD条的link属性 @累积流图|execution|cfd|executionID=%s +- 查看设置菜单第settings条的link属性 @设置|execution|view|executionID=%s + */ $executionTester = new executionTest(); -r($executionTester->setKanbanMenuTest()) && p('kanban:link') && e('看板|execution|kanban|executionID=%s'); // 测试替换看板执行的二级菜单 +r($executionTester->setKanbanMenuTest()) && p('kanban:link') && e('看板|execution|kanban|executionID=%s'); // 测试替换看板执行的二级菜单 +r($executionTester->setKanbanMenuTest()) && p('deliverable:link') && e('交付物|execution|deliverable|executionID=%s'); // 查看交付物菜单 +r($executionTester->setKanbanMenuTest()) && p('build:link') && e('构建|execution|build|executionID=%s'); // 查看构建菜单 +r($executionTester->setKanbanMenuTest()) && p('CFD:link') && e('累积流图|execution|cfd|executionID=%s'); // 查看累积流图菜单 +r($executionTester->setKanbanMenuTest()) && p('settings:link') && e('设置|execution|view|executionID=%s'); // 查看设置菜单 \ No newline at end of file diff --git a/module/file/model.php b/module/file/model.php index 611e7bc02e..5b4732dedc 100755 --- a/module/file/model.php +++ b/module/file/model.php @@ -185,7 +185,7 @@ class fileModel extends model $file['objectID'] = $objectID; $file['addedBy'] = $this->app->user->account; $file['addedDate'] = $now; - $file['extra'] = $extra; + if($extra) $file['extra'] = $extra; unset($file['tmpname']); $this->dao->insert(TABLE_FILE)->data($file)->exec(); $fileTitles[$this->dao->lastInsertId()] = $file['title']; @@ -279,6 +279,7 @@ class fileModel extends model $file['title'] = $purifier->purify($file['title']); $file['size'] = $size[$id]; $file['tmpname'] = $tmp_name[$id]; + $file['extra'] = !empty($extra[$id]) ? $extra[$id] : ''; $files[] = $file; } } @@ -464,12 +465,12 @@ class fileModel extends model /** * Set path name of the uploaded file to be saved. * - * @param int $fileID - * @param string $extension + * @param string|int $fileID + * @param string $extension * @access public * @return string */ - public function setPathName(int $fileID, string $extension): string + public function setPathName(string|int $fileID, string $extension): string { $sessionID = session_id(); $randString = substr($sessionID, mt_rand(0, strlen($sessionID) - 5), 3); diff --git a/module/group/lang/de.php b/module/group/lang/de.php index c5242f8351..8c14b1a702 100644 --- a/module/group/lang/de.php +++ b/module/group/lang/de.php @@ -488,6 +488,7 @@ $lang->group->package->projectBuild = 'Project Build'; $lang->group->package->importCaseLib = 'Import Case Lib'; $lang->group->package->commonSetting = 'Common Setting'; $lang->group->package->stageSetting = 'Stage Setting'; +$lang->group->package->deliverable = 'Deliverable Setting'; $lang->group->package->classify = 'Classify'; $lang->group->package->cmcl = 'Cmcl'; $lang->group->package->auditcl = 'Auditcl'; @@ -620,5 +621,7 @@ $lang->group->package->application = 'Manage Application'; $lang->group->package->component = 'Component'; $lang->group->package->browseRule = 'Browse Rule'; $lang->group->package->manageRule = 'Manage Rule'; +$lang->group->package->executionDeliverable = 'Execution Deliverable'; +$lang->group->package->projectDeliverable = 'Project Deliverable'; include (dirname(__FILE__) . '/resource.php'); diff --git a/module/group/lang/en.php b/module/group/lang/en.php index 0077e8d7b3..2d6cb05ddb 100644 --- a/module/group/lang/en.php +++ b/module/group/lang/en.php @@ -488,6 +488,7 @@ $lang->group->package->projectBuild = 'Project Build'; $lang->group->package->importCaseLib = 'Import Case Lib'; $lang->group->package->commonSetting = 'Common Setting'; $lang->group->package->stageSetting = 'Stage Setting'; +$lang->group->package->deliverable = 'Deliverable Setting'; $lang->group->package->classify = 'Classify'; $lang->group->package->cmcl = 'Cmcl'; $lang->group->package->auditcl = 'Auditcl'; @@ -620,5 +621,7 @@ $lang->group->package->application = 'Manage Application'; $lang->group->package->component = 'Component'; $lang->group->package->browseRule = 'Browse Rule'; $lang->group->package->manageRule = 'Manage Rule'; +$lang->group->package->executionDeliverable = 'Execution Deliverable'; +$lang->group->package->projectDeliverable = 'Project Deliverable'; include (dirname(__FILE__) . '/resource.php'); diff --git a/module/group/lang/fr.php b/module/group/lang/fr.php index 8b10db5e55..035195166c 100644 --- a/module/group/lang/fr.php +++ b/module/group/lang/fr.php @@ -488,6 +488,7 @@ $lang->group->package->projectBuild = 'Project Build'; $lang->group->package->importCaseLib = 'Import Case Lib'; $lang->group->package->commonSetting = 'Common Setting'; $lang->group->package->stageSetting = 'Stage Setting'; +$lang->group->package->deliverable = 'Deliverable Setting'; $lang->group->package->classify = 'Classify'; $lang->group->package->cmcl = 'Cmcl'; $lang->group->package->auditcl = 'Auditcl'; @@ -620,5 +621,7 @@ $lang->group->package->application = 'Manage Application'; $lang->group->package->component = 'Component'; $lang->group->package->browseRule = 'Browse Rule'; $lang->group->package->manageRule = 'Manage Rule'; +$lang->group->package->executionDeliverable = 'Execution Deliverable'; +$lang->group->package->projectDeliverable = 'Project Deliverable'; include (dirname(__FILE__) . '/resource.php'); diff --git a/module/group/lang/zh-cn.php b/module/group/lang/zh-cn.php index 91b990594b..8bc71a8b77 100644 --- a/module/group/lang/zh-cn.php +++ b/module/group/lang/zh-cn.php @@ -488,6 +488,7 @@ $lang->group->package->projectBuild = '项目构建'; $lang->group->package->importCaseLib = '导入用例库'; $lang->group->package->commonSetting = '通用配置'; $lang->group->package->stageSetting = '阶段列表设置'; +$lang->group->package->deliverable = '交付物设置'; $lang->group->package->classify = '分类项设置'; $lang->group->package->cmcl = '审计设置'; $lang->group->package->auditcl = 'QA检查项设置'; @@ -620,5 +621,7 @@ $lang->group->package->application = '管理应用'; $lang->group->package->component = '组件'; $lang->group->package->browseRule = '浏览规则'; $lang->group->package->manageRule = '创建维护规则'; +$lang->group->package->executionDeliverable = '交付物'; +$lang->group->package->projectDeliverable = '交付物'; include (dirname(__FILE__) . '/resource.php'); diff --git a/module/group/packagemanager.php b/module/group/packagemanager.php index 0ebcc691a6..ff458e3d6c 100644 --- a/module/group/packagemanager.php +++ b/module/group/packagemanager.php @@ -65,6 +65,10 @@ $config->group->subset->project = new stdclass(); $config->group->subset->project->order = 170; $config->group->subset->project->nav = 'project'; +$config->group->subset->projectDeliverable = new stdclass(); +$config->group->subset->projectDeliverable->order = 171; +$config->group->subset->projectDeliverable->nav = 'project'; + $config->group->subset->projectplan = new stdclass(); $config->group->subset->projectplan->order = 180; $config->group->subset->projectplan->nav = 'project'; @@ -153,6 +157,10 @@ $config->group->subset->execution = new stdclass(); $config->group->subset->execution->order = 530; $config->group->subset->execution->nav = 'execution'; +$config->group->subset->executionDeliverable = new stdclass(); +$config->group->subset->executionDeliverable->order = 531; +$config->group->subset->executionDeliverable->nav = 'execution'; + $config->group->subset->task = new stdclass(); $config->group->subset->task->order = 540; $config->group->subset->task->nav = 'execution'; @@ -1048,6 +1056,12 @@ $config->group->package->projectWhitelist->privs['project-whitelist'] = ar $config->group->package->projectWhitelist->privs['project-addWhitelist'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,lite', 'order' => 110, 'depend' => array('project-whitelist'), 'recommend' => array('project-unbindWhitelist')); $config->group->package->projectWhitelist->privs['project-unbindWhitelist'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,lite', 'order' => 115, 'depend' => array('project-whitelist'), 'recommend' => array('project-addWhitelist')); +$config->group->package->projectDeliverable = new stdclass(); +$config->group->package->projectDeliverable->order = 5; +$config->group->package->projectDeliverable->subset = 'projectDeliverable'; +$config->group->package->projectDeliverable->privs = array(); +$config->group->package->projectDeliverable->privs['project-deliverable'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 10, 'depend' => array(), 'recommend' => array()); + $config->group->package->browseExecution = new stdclass(); $config->group->package->browseExecution->order = 5; $config->group->package->browseExecution->subset = 'execution'; @@ -1095,6 +1109,13 @@ $config->group->package->executionWhitelist->privs['execution-whitelist'] $config->group->package->executionWhitelist->privs['execution-addWhitelist'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,lite', 'order' => 245, 'depend' => array('execution-whitelist'), 'recommend' => array('execution-unbindWhitelist')); $config->group->package->executionWhitelist->privs['execution-unbindWhitelist'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd,lite', 'order' => 250, 'depend' => array('execution-whitelist'), 'recommend' => array('execution-addWhitelist')); +$config->group->package->executionDeliverable = new stdclass(); +$config->group->package->executionDeliverable->order = 5; +$config->group->package->executionDeliverable->subset = 'executionDeliverable'; +$config->group->package->executionDeliverable->nav = 'execution'; +$config->group->package->executionDeliverable->privs = array(); +$config->group->package->executionDeliverable->privs['execution-deliverable'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 10, 'depend' => array(), 'recommend' => array()); + $config->group->package->gantt = new stdclass(); $config->group->package->gantt->order = 5; $config->group->package->gantt->subset = 'executionview'; @@ -2568,7 +2589,7 @@ $config->group->package->workflowGroup = new stdclass(); $config->group->package->workflowGroup->order = 65; $config->group->package->workflowGroup->subset = 'workflow'; $config->group->package->workflowGroup->privs = array(); -$config->group->package->workflowGroup->privs['workflowgroup-product'] = array('edition' => 'biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 5, 'depend' => array('workflow-browseFlow'), 'recommend' => array()); +$config->group->package->workflowGroup->privs['workflowgroup-product'] = array('edition' => 'biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 5, 'depend' => array('workflow-browseFlow'), 'recommend' => array()); $config->group->package->workflowGroup->privs['workflowgroup-project'] = array('edition' => 'biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 10, 'depend' => array('workflow-browseFlow'), 'recommend' => array()); $config->group->package->workflowGroup->privs['workflowgroup-create'] = array('edition' => 'biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 15, 'depend' => array('workflow-browseFlow'), 'recommend' => array()); $config->group->package->workflowGroup->privs['workflowgroup-edit'] = array('edition' => 'biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 20, 'depend' => array('workflow-browseFlow'), 'recommend' => array()); @@ -2581,6 +2602,7 @@ $config->group->package->workflowGroup->privs['workflowgroup-setExclusive'] = $config->group->package->workflowGroup->privs['workflowgroup-activateFlow'] = array('edition' => 'biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 55, 'depend' => array('workflow-browseFlow'), 'recommend' => array()); $config->group->package->workflowGroup->privs['workflowgroup-deactivateFlow'] = array('edition' => 'biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 60, 'depend' => array('workflow-browseFlow'), 'recommend' => array()); $config->group->package->workflowGroup->privs['workflowgroup-rule'] = array('edition' => 'biz,max,ipd', 'vision' => 'rnd,lite,or', 'order' => 65, 'depend' => array('workflow-browseFlow'), 'recommend' => array()); +$config->group->package->workflowGroup->privs['workflowgroup-deliverable'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 70, 'depend' => array('workflowgroup-project'), 'recommend' => array()); $config->group->package->workflow = new stdclass(); $config->group->package->workflow->order = 5; @@ -2999,6 +3021,16 @@ $config->group->package->stageSetting->privs['stage-edit'] = array('editi $config->group->package->stageSetting->privs['stage-delete'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd', 'order' => 5, 'depend' => array(), 'recommend' => array('stage-create', 'stage-edit')); $config->group->package->stageSetting->privs['stage-plusBrowse'] = array('edition' => 'open,biz,max,ipd', 'vision' => 'rnd', 'order' => 1, 'depend' => array(), 'recommend' => array()); +$config->group->package->deliverable = new stdclass(); +$config->group->package->deliverable->order = 20; +$config->group->package->deliverable->subset = 'modelconfig'; +$config->group->package->deliverable->privs = array(); +$config->group->package->deliverable->privs['deliverable-browse'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 0, 'depend' => array(), 'recommend' => array()); +$config->group->package->deliverable->privs['deliverable-create'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 1, 'depend' => array('deliverable-browse'), 'recommend' => array()); +$config->group->package->deliverable->privs['deliverable-edit'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 2, 'depend' => array('deliverable-browse'), 'recommend' => array('deliverable-view', 'deliverable-browse')); +$config->group->package->deliverable->privs['deliverable-delete'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 3, 'depend' => array('deliverable-browse'), 'recommend' => array('deliverable-view', 'deliverable-browse')); +$config->group->package->deliverable->privs['deliverable-view'] = array('edition' => 'max,ipd', 'vision' => 'rnd', 'order' => 4, 'depend' => array('deliverable-browse'), 'recommend' => array()); + $config->group->package->classify = new stdclass(); $config->group->package->classify->order = 30; $config->group->package->classify->subset = 'modelconfig'; diff --git a/module/programplan/js/create.ui.js b/module/programplan/js/create.ui.js index 7b9e255475..6e61ed13b4 100644 --- a/module/programplan/js/create.ui.js +++ b/module/programplan/js/create.ui.js @@ -230,6 +230,12 @@ window.handleRenderRow = function($row, index, data) $typePicker.render({items: typeItems}); } + /* 已关闭且有交付物的阶段无法变更阶段类型。 */ + $row.find('[data-name="attribute"]').find('.picker-box').on('inited', function(e, info) + { + if(typeof data != 'undefined' && typeof data.hasDeliverable != 'undefined') info[0].render({disabled: true}); + }); + if(project.model == 'ipd') { if(planID == 0 && level == 0) diff --git a/module/programplan/model.php b/module/programplan/model.php index 25f0a28b56..56bd6ba96a 100755 --- a/module/programplan/model.php +++ b/module/programplan/model.php @@ -611,9 +611,9 @@ class programplanModel extends model * @param string $action * @param bool $isParent * @access public - * @return bool + * @return bool|array */ - public function computeProgress(int $stageID, string $action = '', bool $isParent = false): bool + public function computeProgress(int $stageID, string $action = '', bool $isParent = false): bool|array { $stage = $this->loadModel('execution')->fetchByID($stageID); if(empty($stage) || empty($stage->path)) return false; @@ -647,6 +647,17 @@ class programplanModel extends model $newParent = $result['newParent'] ?? null; $parentAction = $result['parentAction'] ?? ''; + /* 如果当前是顶级阶段,并且由于交付物不能关闭,则跳转到顶级阶段的关闭页面。 */ + if(isset($newParent->status) && $newParent->status == 'closed') + { + $isTopStage = $parent->grade == 1 && $parent->type != 'project' && $stageID != $id && $parent->status == 'doing'; + if(in_array($this->config->edition, array('max', 'ipd')) && $isTopStage && !$this->execution->canCloseByDeliverable($parent)) + { + $url = helper::createLink('execution', 'close', "executionID={$parent->id}"); + return array('result' => 'fail', 'callback' => "zui.Modal.confirm('{$this->lang->execution->cannotAutoCloseParent}').then((res) => {if(res) {loadModal('$url', '.modal-dialog');} else {loadPage();}});"); + } + } + /** 更新状态以及记录日志。 */ /** Update status and save log. */ if(isset($newParent) && $newParent) diff --git a/module/programplan/tao.php b/module/programplan/tao.php index aee10c40f6..42ae52ffbc 100644 --- a/module/programplan/tao.php +++ b/module/programplan/tao.php @@ -276,10 +276,12 @@ class programplanTao extends programplanModel $taskTeams = $this->dao->select('task,account')->from(TABLE_TASKTEAM)->where('task')->in(array_keys($tasks))->fetchGroup('task', 'account'); $users = $this->loadModel('user')->getPairs('noletter'); + $firstTask = reset($tasks); + $taskDateLimit = $this->dao->select('taskDateLimit')->from(TABLE_PROJECT)->where('id')->eq($firstTask->project)->fetch('taskDateLimit'); foreach($tasks as $task) { $plan = zget($plans, $task->execution, null); - $dateLimit = $this->getTaskDateLimit($task, $plan); + $dateLimit = $this->getTaskDateLimit($task, $plan, $taskDateLimit == 'limit' ? zget($tasks, $task->parent, null) : null); $data = $this->buildTaskDataForGantt($task, $dateLimit); $data->id = $task->execution . '-' . $task->id; $data->parent = $task->parent > 0 && isset($tasks[$task->parent]) ? $task->execution . '-' . $task->parent : $task->execution; @@ -871,16 +873,23 @@ class programplanTao extends programplanModel * * @param object $task * @param object|null $execution + * @param object|null $parent * @access protected * @return array */ - protected function getTaskDateLimit(object $task, object|null $execution = null): array + protected function getTaskDateLimit(object $task, object|null $execution = null, object|null $parent = null): array { $estStart = helper::isZeroDate($task->estStarted) ? '' : $task->estStarted; $estEnd = helper::isZeroDate($task->deadline) ? '' : $task->deadline; $realBegan = helper::isZeroDate($task->realStarted) ? '' : $task->realStarted; $realEnd = (in_array($task->status, array('done', 'closed')) and !helper::isZeroDate($task->finishedDate)) ? $task->finishedDate : ''; + if($parent) + { + if(empty($estStart) && !helper::isZeroDate($parent->estStarted)) $estStart = $parent->estStarted; + if(empty($estEnd) && !helper::isZeroDate($parent->deadline)) $estEnd = $parent->deadline; + } + $start = $estStart; $end = $estEnd; if(empty($start) and $execution) $start = $execution->begin; diff --git a/module/programplan/ui/edit.html.php b/module/programplan/ui/edit.html.php index 195582d4fa..aef5381878 100644 --- a/module/programplan/ui/edit.html.php +++ b/module/programplan/ui/edit.html.php @@ -45,6 +45,7 @@ formPanel set::name('parent'), set::items($parentStageList), set::value($plan->parent), + set::disabled($hasUploadedDeliverable), set::required(true), on::change('changeParentStage') ) @@ -95,7 +96,7 @@ formPanel ( setID('attributeType'), setClass('flex self-center w-full'), - $enableOptionalAttr ? picker + $enableOptionalAttr && empty($plan->hasDeliverable) ? picker ( setID('attribute'), set::name('attribute'), diff --git a/module/programplan/zen.php b/module/programplan/zen.php index 9f0f4ace2d..4380e3e6f9 100644 --- a/module/programplan/zen.php +++ b/module/programplan/zen.php @@ -297,7 +297,7 @@ class programplanZen extends programplan * @access protected * @return void */ - protected function buildEditView(object $plan) + public function buildEditView(object $plan) { $this->loadModel('project'); $this->loadModel('execution'); @@ -305,17 +305,19 @@ class programplanZen extends programplan $parentStage = $this->project->getByID($plan->parent, 'stage'); - $this->view->title = $this->lang->programplan->edit; - $this->view->isCreateTask = $this->programplan->isCreateTask($plan->id); - $this->view->plan = $plan; - $this->view->project = $this->project->getByID($plan->project); - $this->view->parentStageList = $this->programplan->getParentStageList($plan->project, $plan->id, $plan->product); - $this->view->enableOptionalAttr = empty($parentStage) || (!empty($parentStage) && $parentStage->attribute == 'mix'); - $this->view->isTopStage = $this->programplan->isTopStage($plan->id); - $this->view->isLeafStage = $this->programplan->checkLeafStage($plan->id); - $this->view->PMUsers = $this->loadModel('user')->getPairs('noclosed|nodeleted|pmfirst', $plan->PM); - $this->view->project = $this->project->getByID($plan->project); - $this->view->requiredFields = $this->config->execution->edit->requiredFields; + $this->view->title = $this->lang->programplan->edit; + $this->view->isCreateTask = $this->programplan->isCreateTask($plan->id); + $this->view->plan = $plan; + $this->view->project = $this->project->getByID($plan->project); + $this->view->parentStageList = $this->programplan->getParentStageList($plan->project, $plan->id, $plan->product); + $this->view->enableOptionalAttr = empty($parentStage) || (!empty($parentStage) && $parentStage->attribute == 'mix'); + $this->view->isTopStage = $this->programplan->isTopStage($plan->id); + $this->view->isLeafStage = $this->programplan->checkLeafStage($plan->id); + $this->view->PMUsers = $this->loadModel('user')->getPairs('noclosed|nodeleted|pmfirst', $plan->PM); + $this->view->project = $this->project->getByID($plan->project); + $this->view->requiredFields = $this->config->execution->edit->requiredFields; + $this->view->hasUploadedDeliverable = in_array($this->config->edition, array('max', 'ipd')) ? $this->execution->hasUploadedDeliverable($plan) : false; + $this->display(); } diff --git a/module/project/config/dtable.php b/module/project/config/dtable.php index 97aff15b75..044546eae1 100755 --- a/module/project/config/dtable.php +++ b/module/project/config/dtable.php @@ -97,6 +97,17 @@ $config->project->dtable->fieldList['invested']['group'] = 5; $config->project->dtable->fieldList['invested']['show'] = true; $config->project->dtable->fieldList['invested']['sortType'] = false; +if(helper::hasFeature('deliverable') && in_array($config->edition, array('max', 'ipd'))) +{ + $config->project->dtable->fieldList['deliverable']['title'] = $lang->project->deliverableAbbr; + $config->project->dtable->fieldList['deliverable']['name'] = 'deliverable'; + $config->project->dtable->fieldList['deliverable']['type'] = 'html'; + $config->project->dtable->fieldList['deliverable']['width'] = '120px'; + $config->project->dtable->fieldList['deliverable']['group'] = 5; + $config->project->dtable->fieldList['deliverable']['show'] = true; + $config->project->dtable->fieldList['deliverable']['sortType'] = false; +} + $config->project->dtable->fieldList['begin']['title'] = $lang->project->begin; $config->project->dtable->fieldList['begin']['name'] = 'begin'; $config->project->dtable->fieldList['begin']['type'] = 'date'; @@ -188,6 +199,17 @@ $config->project->execution->dtable->fieldList['status']['width'] = '80'; $config->project->execution->dtable->fieldList['status']['group'] = '1'; $config->project->execution->dtable->fieldList['status']['show'] = true; +if(helper::hasFeature('deliverable') && in_array($config->edition, array('max', 'ipd'))) +{ + $config->project->execution->dtable->fieldList['deliverable']['title'] = $lang->project->deliverableAbbr; + $config->project->execution->dtable->fieldList['deliverable']['name'] = 'deliverable'; + $config->project->execution->dtable->fieldList['deliverable']['type'] = 'html'; + $config->project->execution->dtable->fieldList['deliverable']['width'] = '100px'; + $config->project->execution->dtable->fieldList['deliverable']['group'] = '1'; + $config->project->execution->dtable->fieldList['deliverable']['show'] = true; + $config->project->execution->dtable->fieldList['deliverable']['sortType'] = false; +} + $config->project->execution->dtable->fieldList['PM']['title'] = $lang->project->PM; $config->project->execution->dtable->fieldList['PM']['name'] = 'PM'; $config->project->execution->dtable->fieldList['PM']['type'] = 'avatarBtn'; diff --git a/module/project/config/form.php b/module/project/config/form.php index 143cfdf679..56bcdab02a 100755 --- a/module/project/config/form.php +++ b/module/project/config/form.php @@ -36,6 +36,7 @@ $config->project->form->edit = $config->project->form->create; $config->project->form->edit['products'] = array('type' => 'array', 'required' => false, 'default' => array()); $config->project->form->edit['branch'] = array('type' => 'array', 'required' => false, 'default' => array()); $config->project->form->edit['plans'] = array('type' => 'array', 'required' => false, 'default' => array()); +$config->project->form->edit['taskDateLimit'] = array('type' => 'string', 'required' => false, 'default' => array()); unset($config->project->form->edit['hasProduct']); unset($config->project->form->edit['stageBy']); unset($config->project->form->edit['multiple']); diff --git a/module/project/tao.php b/module/project/tao.php index b941368228..3ba79f722f 100755 --- a/module/project/tao.php +++ b/module/project/tao.php @@ -1050,6 +1050,7 @@ class projectTao extends projectModel include $this->app->getModulePath('', 'execution') . 'lang/' . $this->app->getClientLang() . '.php'; + if($projectModel == 'ipd') unset($lang->waterfall->menu->other['dropMenu']->deliverable); $lang->execution->typeList['sprint'] = $executionCommonLang; } elseif($projectModel == 'kanban') diff --git a/module/project/test/model/addplans.php b/module/project/test/model/addplans.php index 7cab569203..5f9e6c3418 100755 --- a/module/project/test/model/addplans.php +++ b/module/project/test/model/addplans.php @@ -8,7 +8,9 @@ zenData('product')->gen(100); zenData('productplan')->gen(100); $storyTable = zenData('story'); $storyTable->status->range('active'); +$storyTable->version->range('1'); $storyTable->gen(100); +zenData('storyspec')->gen(100); zenData('planstory')->gen(100); zenData('projectstory')->gen(0); @@ -20,6 +22,15 @@ title=测试 projectModel::addPlans(); timeout=0 cid=1 +- 将计划1,4,7下的需求关联到项目13,查看关联后的需求数 @16 +- 将计划1,4,7下的需求关联到项目13,查看关联后的需求ID/产品ID + - 第3条的story属性 @4 + - 第3条的product属性 @1 +- 将计划2,5,10,13下的需求关联到项目11,查看关联后的需求数 @8 +- 将计划2,5,10,13下的需求关联到项目11,查看关联后的需求ID/产品ID + - 第0条的story属性 @13 + - 第0条的product属性 @4 + */ $project = new Project(); @@ -32,4 +43,4 @@ r($project->addPlansTest(13, $plan)) && p('3:story,product') && e('4,1'); // 将 $plan = array(); $plan[1] = array(2, 5, 10, 13); r(count($project->addPlansTest(11, $plan))) && p('') && e('8'); // 将计划2,5,10,13下的需求关联到项目11,查看关联后的需求数 -r($project->addPlansTest(11, $plan)) && p('0:story,product') && e('13,4'); // 将计划2,5,10,13下的需求关联到项目11,查看关联后的需求ID/产品ID +r($project->addPlansTest(11, $plan)) && p('0:story,product') && e('13,4'); // 将计划2,5,10,13下的需求关联到项目11,查看关联后的需求ID/产品ID \ No newline at end of file diff --git a/module/project/test/model/batchupdate.php b/module/project/test/model/batchupdate.php index 91824daaf1..4ad7533a3a 100755 --- a/module/project/test/model/batchupdate.php +++ b/module/project/test/model/batchupdate.php @@ -29,6 +29,27 @@ title=测试taskModel->batchUpdate(); timeout=0 cid=1 +- 查看被编辑了的项目数量 @3 +- 查看被编辑了的项目11详情 + - 第1条的name属性 @批量修改项目11 + - 第1条的parent属性 @1 + - 第1条的PM属性 @user10 + - 第1条的begin属性 @2022-02-08 + - 第1条的acl属性 @open +- 查看被编辑了的项目12详情 + - 第2条的name属性 @批量修改项目12 + - 第2条的parent属性 @2 + - 第2条的PM属性 @user11 + - 第2条的begin属性 @2022-03-05 + - 第2条的acl属性 @private +- 查看被编辑了的项目13详情 + - 第3条的name属性 @批量修改项目13 + - 第3条的parent属性 @3 + - 第3条的PM属性 @user13 + - 第3条的begin属性 @2022-02-19 + - 第3条的acl属性 @program +- 异常情况第message[end]条的0属性 @ID4『计划完成』应当大于『2023-02-19』。 + */ $project = new Project(); @@ -40,7 +61,7 @@ $data[1]->parent = 1; $data[1]->PM = 'user10'; $data[1]->begin = '2022-02-08'; $data[1]->end = '2022-04-13'; -$data[1]->day = 10; +$data[1]->days = 10; $data[1]->acl = 'open'; $data[2] = new stdClass(); @@ -49,7 +70,7 @@ $data[2]->parent = 2; $data[2]->PM = 'user11'; $data[2]->begin = '2022-03-05'; $data[2]->end = '2022-04-13'; -$data[2]->day = 10; +$data[2]->days = 10; $data[2]->acl = 'private'; $data[3] = new stdClass(); @@ -58,7 +79,7 @@ $data[3]->parent = 3; $data[3]->PM = 'user13'; $data[3]->begin = '2022-02-19'; $data[3]->end = '2022-04-13'; -$data[3]->day = 14; +$data[3]->days = 14; $data[3]->acl = 'program'; $projects = $project->batchUpdate($data); @@ -79,4 +100,4 @@ $data[4]->day = 14; $data[4]->acl = 'program'; $projects = $project->batchUpdate($data); -r($projects) && p('message[end]:0') && e('ID4『计划完成』应当大于『2023-02-19』。'); // 异常情况 +r($projects) && p('message[end]:0') && e('ID4『计划完成』应当大于『2023-02-19』。'); // 异常情况 \ No newline at end of file diff --git a/module/project/test/model/getdisabledproducts.php b/module/project/test/model/getdisabledproducts.php index 2fd5c5cb4a..aa45dc5eb8 100644 --- a/module/project/test/model/getdisabledproducts.php +++ b/module/project/test/model/getdisabledproducts.php @@ -9,6 +9,13 @@ title=测试 projectModel->getDisabledProducts(); timeout=0 cid=1 +- 测试获取敏捷项目不可修改产品 @0 +- 测试获取以产品创建的瀑布项目不可修改产品属性2 @项目已经关联了该产品中的研发需求,不能取消关联,您可以取消关联研发需求后再操作。 +- 测试获取以产品创建的瀑布项目不可修改产品属性3 @该产品已经创建了阶段,如需解除与项目的关联,请删除已创建的阶段后再操作。 +- 测试获取以产品创建的瀑布项目不可修改产品属性4 @该产品已经创建了阶段并关联了研发需求,如需解除与项目的关联,请先解除研发需求的关联关系,然后删除已创建的阶段后再操作。 +- 测试获取以项目创建的瀑布项目不可修改产品属性5 @该产品的研发需求已经关联到了项目和执行中,请先解除研发需求与项目和执行的关联后再操作。 +- 测试获取以项目创建的瀑布项目不可修改产品属性6 @项目已经关联了该产品中的研发需求,不能取消关联,您可以取消关联研发需求后再操作。 + */ zenData('project')->loadYaml('project')->gen(10); @@ -24,4 +31,4 @@ r($projectTester->getDisabledProductsTest($projectIdList[1])) && p('2') && e(' r($projectTester->getDisabledProductsTest($projectIdList[2])) && p('3') && e('该产品已经创建了阶段,如需解除与项目的关联,请删除已创建的阶段后再操作。'); // 测试获取以产品创建的瀑布项目不可修改产品 r($projectTester->getDisabledProductsTest($projectIdList[3])) && p('4') && e('该产品已经创建了阶段并关联了研发需求,如需解除与项目的关联,请先解除研发需求的关联关系,然后删除已创建的阶段后再操作。'); // 测试获取以产品创建的瀑布项目不可修改产品 r($projectTester->getDisabledProductsTest($projectIdList[4])) && p('5') && e('该产品的研发需求已经关联到了项目和执行中,请先解除研发需求与项目和执行的关联后再操作。'); // 测试获取以项目创建的瀑布项目不可修改产品 -r($projectTester->getDisabledProductsTest($projectIdList[5])) && p('6') && e('项目已经关联了该产品中的研发需求,不能取消关联,您可以取消关联研发需求后再操作。'); // 测试获取以项目创建的瀑布项目不可修改产品 +r($projectTester->getDisabledProductsTest($projectIdList[5])) && p('6') && e('项目已经关联了该产品中的研发需求,不能取消关联,您可以取消关联研发需求后再操作。'); // 测试获取以项目创建的瀑布项目不可修改产品 \ No newline at end of file diff --git a/module/project/test/model/yaml/getdisabledproducts/project.yaml b/module/project/test/model/yaml/getdisabledproducts/project.yaml index 2cf2a1d34d..f1aaef5293 100644 --- a/module/project/test/model/yaml/getdisabledproducts/project.yaml +++ b/module/project/test/model/yaml/getdisabledproducts/project.yaml @@ -7,7 +7,7 @@ fields: - field: type range: project{6},stage{6} - field: stageBy - range: "product{4},project{2},[]{6}" + range: "product{4},project{2}" - field: project range: '0{6},3{2},4,5' - field: model diff --git a/module/project/test/tao/setmenubymodel.php b/module/project/test/tao/setmenubymodel.php index 37648e686f..391d2b8b4d 100644 --- a/module/project/test/tao/setmenubymodel.php +++ b/module/project/test/tao/setmenubymodel.php @@ -9,8 +9,16 @@ su('admin'); /** title=测试 projectModel::setMenuByModel(); +timeout=0 cid=1 -pid=1 + +- 项目不存在的情况 @迭代 +- 项目不存在的情况 @迭代 +- 敏捷项目 @迭代 +- 融合敏捷项目 @迭代 +- 融合瀑布项目 @阶段 +- 瀑布项目 @阶段 +- 看板项目 @项目看板 */ @@ -22,4 +30,4 @@ r($projectTester->setMenuByModelTest('scrum')) && p() && e('迭代'); r($projectTester->setMenuByModelTest('agileplus')) && p() && e('迭代'); // 融合敏捷项目 r($projectTester->setMenuByModelTest('waterfall')) && p() && e('阶段'); // 融合瀑布项目 r($projectTester->setMenuByModelTest('waterfallplus')) && p() && e('阶段'); // 瀑布项目 -r($projectTester->setMenuByModelTest('kanban')) && p() && e('项目看板'); // 看板项目 +r($projectTester->setMenuByModelTest('kanban')) && p() && e('项目看板'); // 看板项目 \ No newline at end of file diff --git a/module/project/ui/browsebycard.html.php b/module/project/ui/browsebycard.html.php index 67456d5ae4..e2b28d4efa 100644 --- a/module/project/ui/browsebycard.html.php +++ b/module/project/ui/browsebycard.html.php @@ -44,7 +44,7 @@ featureBar set::text($lang->project->mine), set::checked($this->cookie->involved ? 'checked' : '') ), - li(searchToggle(set::module('project'))) + li(searchToggle(set::module('project'), set::open($browseType == 'bysearch'))) ); /* zin: Define the toolbar on main menu. */ diff --git a/module/project/ui/browsebylist.html.php b/module/project/ui/browsebylist.html.php index f2ea2480d0..5d64b2314e 100644 --- a/module/project/ui/browsebylist.html.php +++ b/module/project/ui/browsebylist.html.php @@ -25,7 +25,7 @@ featureBar set::text($lang->project->mine), set::checked($this->cookie->involved ? 'checked' : '') ), - li(searchToggle(set::module('project'))) + li(searchToggle(set::module('project'), set::open($browseType == 'bysearch'))) ); /* zin: Define the toolbar on main menu. */ diff --git a/module/project/ui/create.field.php b/module/project/ui/create.field.php index 64f776a22a..3e761971fe 100644 --- a/module/project/ui/create.field.php +++ b/module/project/ui/create.field.php @@ -60,6 +60,8 @@ $fields->field('acl') $fields->field('auth')->foldable()->value($copyProject ? data('copyProject.auth') : 'extend'); +$fields->field('taskDateLimit')->hidden(true)->value('auto'); + $storyType = in_array($model, array('waterfall', 'waterfallplus', 'ipd')) ? 'story,requirement' : 'story'; if($copyProject) $storyType = data('copyProject.storyType'); $fields->field('taskDateLimit')->width('full')->foldable()->value('auto'); diff --git a/module/project/zen.php b/module/project/zen.php index ec118dcc37..dc54beeaa7 100755 --- a/module/project/zen.php +++ b/module/project/zen.php @@ -484,6 +484,12 @@ class projectZen extends project $this->view->programID = $programID; $this->view->disableParent = $disableParent; $this->view->groups = $this->loadModel('group')->getPairs(); + + if(in_array($this->config->edition, array('max', 'ipd')) && $this->project->checkUploadedDeliverable($project)) + { + $this->view->disableModel = true; + } + $this->display(); } @@ -1393,6 +1399,12 @@ class projectZen extends project $project->estimate = helper::formatHours($project->estimate); $project->consume = helper::formatHours($project->consume); $project->left = helper::formatHours($project->left); + + /* 交付物提交进度。 */ + if(in_array($this->config->edition, array('max', 'ipd'))) + { + $project->deliverable = $this->project->countDeliverable($project); + } } return array_values($projectList); diff --git a/module/story/ui/edit.html.php b/module/story/ui/edit.html.php index ccf24c8f39..09f33d0351 100644 --- a/module/story/ui/edit.html.php +++ b/module/story/ui/edit.html.php @@ -197,6 +197,7 @@ detailBody ), section ( + setID('comment'), set::title($lang->story->comment), formGroup(editor(set::name('comment'), set::uid($uid))) ) diff --git a/module/task/js/batchedit.ui.js b/module/task/js/batchedit.ui.js index 8c08ad6d95..c4539c74f4 100644 --- a/module/task/js/batchedit.ui.js +++ b/module/task/js/batchedit.ui.js @@ -196,6 +196,7 @@ function checkBatchEstStartedAndDeadline(event) $estStartedTd.find('.date-tip').remove(); const $childrenEstStarted = $(event.target).closest('tbody').find('tr[data-parent="' + taskID + '"]').find('[name^=estStarted]'); + $childrenEstStarted.each(function() { let $childDatePicker = $(this).zui('datePicker'); @@ -204,6 +205,7 @@ function checkBatchEstStartedAndDeadline(event) checkBatchEstStartedAndDeadline({target: this}); }); + if(estStarted.length > 0) { let $datetip = $('
'); diff --git a/module/transfer/ui/import.html.php b/module/transfer/ui/import.html.php index cbf556678e..5a29070e0f 100644 --- a/module/transfer/ui/import.html.php +++ b/module/transfer/ui/import.html.php @@ -22,6 +22,6 @@ formPanel set::items($typeList) ) : null, input(set::type('file'), set::name('file')), - span(setClass('label secondary'), $lang->transfer->importNotice) + span(setClass('label secondary h-auto'), $lang->transfer->importNotice) ); h::js('$.cookie.set("maxImport", 0, {expires:config.cookieLife, path:config.webRoot});'); diff --git a/module/tutorial/model.php b/module/tutorial/model.php index 0bacf79939..acb2d40b54 100644 --- a/module/tutorial/model.php +++ b/module/tutorial/model.php @@ -186,6 +186,7 @@ class tutorialModel extends model $project->charter = 0; $project->market = 1; $project->budgetUnit = 'CNY'; + $project->deliverable = ''; list($guide, $guideTask, $guideStepIndex) = empty($_SERVER['HTTP_X_ZIN_TUTORIAL']) ? array('', '', '') : explode('-', $_SERVER['HTTP_X_ZIN_TUTORIAL']); if($guide && strpos($guide, 'scrumProjectManage') !== false) @@ -306,6 +307,7 @@ class tutorialModel extends model $execution->type = 'sprint'; $execution->projectName = ''; $execution->projectModel = 'scrum'; + $execution->deliverable = ''; if($browseType && $browseType != 'all') $execution->name .= '-' . $browseType; diff --git a/module/upgrade/config.php b/module/upgrade/config.php index e0021b04e8..172c5af02d 100644 --- a/module/upgrade/config.php +++ b/module/upgrade/config.php @@ -1476,6 +1476,13 @@ $config->delete['21_6_beta'][] = 'extension/ipd/common/ext/lang/en/effort.php'; $config->delete['21_6_beta'][] = 'extension/ipd/common/ext/lang/fr/effort.php'; $config->delete['21_6_beta'][] = 'extension/ipd/common/ext/lang/zh-cn/effort.php'; +$config->delete['21_6_beta'][] = 'extension/ipd/project/ext/model/class/zentaoipd.class.php'; +$config->delete['21_6_beta'][] = 'extension/max/project/ext/model/class/zentaoipd.class.php'; +$config->delete['21_6_beta'][] = 'extension/biz/project/ext/model/class/zentaoipd.class.php'; +$config->delete['21_6_beta'][] = 'extension/ipd/project/ext/model/zentaoipd.php'; +$config->delete['21_6_beta'][] = 'extension/max/project/ext/model/zentaoipd.php'; +$config->delete['21_6_beta'][] = 'extension/biz/project/ext/model/zentaoipd.php'; + $config->upgrade->openModules = array('action', 'admin', 'ai', 'bi', 'aiapp', 'api', 'automation', 'backup', 'block', 'branch', 'budget', 'bug', 'build', 'cache', 'caselib', 'chart', 'ci', 'client', 'common', 'company', 'compile', 'convert', 'cron', 'custom', 'datatable', 'dataview', 'dept', 'design', 'dev', 'dimension', 'doc', 'durationestimation', 'entry', 'execution', 'extension', 'file', 'git', 'gitlab', 'group', 'holiday', 'im', 'index', 'index.html', 'install', 'issue', 'jenkins', 'job', 'kanban', 'license', 'mail', 'message', 'metric', 'misc', 'mr', 'my', 'personnel', 'pipeline', 'product', 'productplan', 'productset', 'program', 'programplan', 'project', 'projectbuild', 'projectplan', 'projectrelease', 'projectstory', 'pivot', 'qa', 'release', 'repo', 'report', 'risk', 'score', 'screen', 'search', 'setting', 'sonarqube', 'sso', 'stage', 'stakeholder', 'story', 'subject', 'svn', 'task', 'testcase', 'testreport', 'testsuite', 'testtask', 'todo', 'tree', 'tutorial', 'upgrade', 'user', 'webhook', 'weekly', 'workestimation', 'gitea', 'gogs', 'transfer', 'zahost', 'zanode', 'editor', 'charter', 'roadmap', 'account', 'cne', 'host', 'instance', 'ops', 'serverroom', 'space', 'store', 'system', 'solution', 'demand', 'gitfox', 'epic', 'requirement', 'mark'); $config->upgrade->unsetModules = array('design', 'program', 'programplan', 'projectbuild', 'projectrelease', 'stage', 'stakeholder', 'product', 'branch', 'productplan', 'release', 'build', 'qa', 'bug', 'testcase', 'testtask', 'testreport', 'testsuite', 'caselib', 'automation', 'repo', 'ci', 'compile', 'jenkins', 'job', 'svn', 'gitlab', 'sonarqube', 'mr', 'git', 'report', 'sqlbuilder', 'feedback', 'faq', 'attend', 'holiday', 'leave', 'makeup', 'overtime', 'lieu', 'ops', 'host', 'serverroom', 'account', 'domain', 'service', 'deploy', 'conference', 'traincourse', 'pssp', 'baseline', 'classify', 'cm', 'cmcl', 'auditcl', 'reviewcl', 'process', 'activity', 'zoutput', 'auditplan', 'nc', 'subject', 'weekly', 'workestimation', 'issue', 'durationestimation', 'risk', 'opportunity', 'trainplan', 'gapanalysis', 'researchplan', 'researchreport', 'meeting', 'meetingroom', 'budget', 'reviewissue', 'reviewsetting', 'review', 'milestone', 'measurement', 'measrecord', 'assetlib', 'setting', 'im', 'client', 'ldap', 'dev', 'api', 'gitea', 'gogs', 'zanode', 'zahost'); diff --git a/module/user/model.php b/module/user/model.php index 9893929b71..5b8161c6fc 100644 --- a/module/user/model.php +++ b/module/user/model.php @@ -1298,6 +1298,7 @@ class userModel extends model */ public function getProjects(string $account, string $status = 'all', string $orderBy = 'id_desc', object $pager = null): array { + $this->loadModel('project'); $projects = $this->userTao->fetchProjects($account, $status, $orderBy, $pager); if(!$projects) return array(); @@ -1317,6 +1318,8 @@ class userModel extends model $project->storyPoints = $projectStory ? round($projectStory->estimate, 1) : 0; $project->storyCount = $projectStory ? $projectStory->count : 0; $project->executionCount = zget($projectExecutionCount, $project->id, 0); + + if(in_array($this->config->edition, array('max', 'ipd'))) $project->deliverable = $this->project->countDeliverable($project); } return $projects; diff --git a/test/data/deliverable.yaml b/test/data/deliverable.yaml new file mode 100644 index 0000000000..ee40d120cc --- /dev/null +++ b/test/data/deliverable.yaml @@ -0,0 +1,34 @@ +title: table zt_deliverable +desc: "交付物" +author: Sun Guangming +version: "1.0" +fields: + - field: id + note: "ID" + range: 1-1000 + - field: name + fields: + - field: name1 + range: '交付物' + - field: name2 + range: 1-1000 + - field: module + range: project,execution + - field: method + range: create,close + - field: model + fields: + - field: model1 + range: product,project + postfix: "_" + - field: model2 + range: waterfall,scrum + postfix: "_" + - field: model3 + range: mix,request,design,dev,qa,release,review + - field: type + range: doc,file + - field: files + range: 1-10 + - field: desc + range: '交付物描述' \ No newline at end of file