diff --git a/lib/zin/func.php b/lib/zin/func.php index 4df2fdb7fc..c453ec27d0 100644 --- a/lib/zin/func.php +++ b/lib/zin/func.php @@ -963,3 +963,14 @@ function floatToolbar() { return createWg('floatToolbar', func_get_args()); } + +/** + * zentao data group. + * + * string label, + * string labelClass? + */ +function dataGroup() +{ + return createWg('dataGroup', func_get_args()); +} diff --git a/lib/zin/wg/datagroup/v1.php b/lib/zin/wg/datagroup/v1.php new file mode 100644 index 0000000000..903a40a114 --- /dev/null +++ b/lib/zin/wg/datagroup/v1.php @@ -0,0 +1,34 @@ +prop('labelClass'); + + return div + ( + setClass($className, 'text-gray', 'text-right', 'w-16'), + $text + ); + } + + protected function build() + { + $text = $this->prop('label'); + + return div + ( + set($this->props->skip(array_keys(static::getDefinedProps()))), + setClass('data-group flex justify-start gap-2 my-4'), + $this->label($text), + $this->children(), + ); + } +} diff --git a/module/block/control.php b/module/block/control.php index f8a3d8cdc3..4d8d9c0644 100755 --- a/module/block/control.php +++ b/module/block/control.php @@ -228,16 +228,20 @@ class block extends control */ public function dashboard(string $dashboard, string $projectID = '0') { - $projectID = (int)$projectID; + $projectID = (int)$projectID; // 强制转换为 int 类型,防止调用 model、tao 方法时报错 + + /* 获取传入应用对应的区块列表 以及 获取当前应用下区块启用状态 */ $blocks = $this->block->getMyDashboard($dashboard); $isInitiated = $this->block->fetchBlockInitStatus($dashboard); - /* Init block when vist index first. */ - if(empty($blocks) and !$isInitiated and !defined('TUTORIAL') and $this->block->initBlock($dashboard)) + /* 判断用户是否为首次登录 ,判断条件 当前用户没有该 app 下的区块数据 且 没有设置过该 app 下的区块启用状态 且不是演示模式 */ + if(empty($blocks) and !$isInitiated and !defined('TUTORIAL')) { - return print(js::reload()); + $this->block->initBlock($dashboard); // 初始化该 app 下区块数据 + $blocks = $this->block->getMyDashboard($dashboard); // 获取初始化后的区块列表 } + /* */ $blocks = $this->blockZen->processBlockForRender($blocks, $projectID); if($this->app->getViewType() == 'json') return print(json_encode($blocks)); diff --git a/module/block/model.php b/module/block/model.php index f776c11294..32764c870f 100644 --- a/module/block/model.php +++ b/module/block/model.php @@ -298,7 +298,7 @@ class blockModel extends model /** * Reset dashboard blocks. * - * @param string $dashboard + * @param string $dashboard * @access public * @return bool */ @@ -323,7 +323,7 @@ class blockModel extends model /** * Hidden a block. * - * @param int $blockID + * @param int $blockID * @access public * @return bool */ @@ -341,7 +341,7 @@ class blockModel extends model /** * Delete a block. * - * @param int $blockID + * @param int $blockID * @access public * @return bool */ @@ -358,9 +358,9 @@ class blockModel extends model /** * Set block order. - * - * @param int $blockID - * @param int $order + * + * @param int $blockID + * @param int $order * @access public * @return bool */ diff --git a/module/block/tao.php b/module/block/tao.php index bbc9191f0a..716d91b0f9 100644 --- a/module/block/tao.php +++ b/module/block/tao.php @@ -3,10 +3,10 @@ declare(strict_types=1); class blockTao extends blockModel { /** - * Get block list of current user. * 获取当前用户的区块列表. + * Get block list of current user. * - * @param string $module + * @param string $dashboard * @param int $hidden 0|1 * @access protected * @return int[]|false diff --git a/module/block/zen.php b/module/block/zen.php index 447873f998..2703d35e2d 100644 --- a/module/block/zen.php +++ b/module/block/zen.php @@ -153,47 +153,50 @@ class blockZen extends block */ protected function processBlockForRender(array $blocks, int $projectID): array { + /* 根据用户的权限,和当前系统开启的权限 处理区块列表 */ $acls = $this->app->user->rights['acls']; foreach($blocks as $key => $block) { + /* 将没有开启功能区块过虑 */ if($block->code == 'waterfallrisk' and !helper::hasFeature("waterfall_risk")) continue; if($block->code == 'waterfallissue' and !helper::hasFeature("waterfall_issue")) continue; if($block->code == 'scrumrisk' and !helper::hasFeature("scrum_risk")) continue; if($block->code == 'scrumissue' and !helper::hasFeature("scrum_issue")) continue; - if(!empty($block->source) and $block->source != 'todo' and !empty($acls['views']) and !isset($acls['views'][$block->source])) + /* 将没有视图权限的区块过滤 */ + if(!empty($block->module) and $block->module != 'todo' and !empty($acls['views']) and !isset($acls['views'][$block->module])) { unset($blocks[$key]); continue; } + /* 处理 params 信息中 count 的值,当没有 count 字段时 ,将 num 字段赋值给 count */ $block->params = json_decode($block->params); if(isset($block->params->num) and !isset($block->params->count)) $block->params->count = $block->params->num; - $this->getBlockMoreLink($block, $projectID); + /* 补全加载链接 */ + $this->computeMoreLink($block, $projectID); } - return $blocks; } /** - * 获取区块的更多链接。 + * 补全区块的加载链接。 * Get the more link of the block. * * @param object $block * @param int $projectID * @return void */ - private function getBlockMoreLink(object $block, int $projectID): void + private function computeMoreLink(object $block, int $projectID): void { - $code = $block->code; - $source = empty($block->source) ? 'common' : $block->source; + $module = empty($block->module) ? 'common' : $block->module; $block->blockLink = $this->createLink('block', 'printBlock', "id=$block->id&module=$block->module"); $block->moreLink = ''; - if(isset($this->config->block->modules[$source]->moreLinkList->{$code})) + if(isset($this->config->block->modules[$module]->moreLinkList->{$block->code})) { - list($moduleName, $method, $vars) = explode('|', sprintf($this->config->block->modules[$source]->moreLinkList->{$code}, isset($block->params->type) ? $block->params->type : '')); + list($moduleName, $method, $vars) = explode('|', sprintf($this->config->block->modules[$module]->moreLinkList->{$block->code}, isset($block->params->type) ? $block->params->type : '')); /* The list assigned to me jumps to the work page when click more button. */ $block->moreLink = $this->createLink($moduleName, $method, $vars); diff --git a/module/branch/model.php b/module/branch/model.php index e7983045fa..ac31f690b5 100644 --- a/module/branch/model.php +++ b/module/branch/model.php @@ -409,6 +409,7 @@ class branchModel extends model if(is_array($productID)) { foreach($productID as $id) $this->linkBranch4Project($id); + return; } $linkedBranchProject = array(); diff --git a/module/product/config/form.php b/module/product/config/form.php index 0e23310aa9..ae2802aef3 100644 --- a/module/product/config/form.php +++ b/module/product/config/form.php @@ -16,18 +16,33 @@ $config->product->form->create['status'] = array('type' => 'string', 'contro $config->product->form->create['desc'] = array('type' => 'string', 'control' => 'editor', 'required' => false, 'default' => '', 'width' => 'full'); $config->product->form->create['acl'] = array('type' => 'string', 'control' => 'radio', 'required' => false, 'default' => 'private', 'width' => 'full', 'options' => $lang->product->aclList); $config->product->form->create['whitelist'] = array('type' => 'array', 'control' => 'multi-select', 'required' => false, 'default' => '', 'filter' => 'join', 'width' => 'full', 'options' => 'users'); +if($config->systemMode != 'ALM') unset($config->product->form->create['program'], $config->product->form->create['line'], $config->product->form->create['lineName']); $config->product->form->edit = array(); -$config->product->form->edit['program'] = array('type' => 'int', 'control' => 'select', 'required' => false, 'default' => 0, 'options' => array()); -$config->product->form->edit['line'] = array('type' => 'int', 'control' => 'select', 'required' => false, 'default' => 0, 'options' => array()); -$config->product->form->edit['name'] = array('type' => 'string', 'control' => 'text', 'required' => true, 'default' => '', 'filter' => 'trim'); -$config->product->form->edit['code'] = array('type' => 'string', 'control' => 'text', 'required' => true, 'default' => '', 'filter' => 'trim'); -$config->product->form->edit['PO'] = array('type' => 'account', 'control' => 'select', 'required' => false, 'default' => '', 'options' => array()); -$config->product->form->edit['QD'] = array('type' => 'account', 'control' => 'select', 'required' => false, 'default' => '', 'options' => array()); -$config->product->form->edit['RD'] = array('type' => 'account', 'control' => 'select', 'required' => false, 'default' => '', 'options' => array()); -$config->product->form->edit['reviewer'] = array('type' => 'array', 'control' => 'multi-select', 'required' => false, 'default' => '', 'filter' => 'join', 'options' => 'users'); -$config->product->form->edit['type'] = array('type' => 'string', 'control' => 'select', 'required' => false, 'default' => 'normal', 'options' => $lang->product->typeList); -$config->product->form->edit['status'] = array('type' => 'string', 'control' => 'select', 'required' => false, 'default' => 'normal', 'options' => $lang->product->statusList); -$config->product->form->edit['desc'] = array('type' => 'string', 'control' => 'textarea', 'required' => false, 'default' => ''); -$config->product->form->edit['acl'] = array('type' => 'string', 'control' => 'radio', 'required' => false, 'default' => 'private', 'options' => $lang->product->aclList); -$config->product->form->edit['whitelist'] = array('type' => 'array', 'control' => 'multi-select', 'required' => false, 'default' => '', 'filter' => 'join', 'options' => 'users'); +$config->product->form->edit['program'] = array('type' => 'int', 'control' => 'select', 'required' => false, 'default' => 0, 'options' => array()); +$config->product->form->edit['line'] = array('type' => 'int', 'control' => 'select', 'required' => false, 'default' => 0, 'options' => array()); +$config->product->form->edit['name'] = array('type' => 'string', 'control' => 'text', 'required' => true, 'default' => '', 'filter' => 'trim'); +$config->product->form->edit['code'] = array('type' => 'string', 'control' => 'text', 'required' => true, 'default' => '', 'filter' => 'trim'); +$config->product->form->edit['PO'] = array('type' => 'account', 'control' => 'select', 'required' => false, 'default' => '', 'options' => array()); +$config->product->form->edit['QD'] = array('type' => 'account', 'control' => 'select', 'required' => false, 'default' => '', 'options' => array()); +$config->product->form->edit['RD'] = array('type' => 'account', 'control' => 'select', 'required' => false, 'default' => '', 'options' => array()); +$config->product->form->edit['reviewer'] = array('type' => 'array', 'control' => 'multi-select', 'required' => false, 'default' => '', 'filter' => 'join', 'options' => 'users'); +$config->product->form->edit['type'] = array('type' => 'string', 'control' => 'select', 'required' => false, 'default' => 'normal', 'options' => $lang->product->typeList); +$config->product->form->edit['status'] = array('type' => 'string', 'control' => 'select', 'required' => false, 'default' => 'normal', 'options' => $lang->product->statusList); +$config->product->form->edit['desc'] = array('type' => 'string', 'control' => 'editor', 'required' => false, 'default' => ''); +$config->product->form->edit['acl'] = array('type' => 'string', 'control' => 'radio', 'required' => false, 'default' => 'private', 'options' => $lang->product->aclList); +$config->product->form->edit['whitelist'] = array('type' => 'array', 'control' => 'multi-select', 'required' => false, 'default' => '', 'filter' => 'join', 'options' => 'users'); +if($config->systemMode != 'ALM') unset($config->product->form->edit['program'], $config->product->form->edit['line']); + +$config->product->form->batchEdit = array(); +$config->product->form->batchEdit['program'] = array('type' => 'array', 'control' => 'select', 'required' => false, 'default' => 0, 'options' => array()); +$config->product->form->batchEdit['name'] = array('type' => 'array', 'control' => 'text', 'required' => true, 'default' => ''); +$config->product->form->batchEdit['line'] = array('type' => 'array', 'control' => 'select', 'required' => false, 'default' => 0, 'options' => array()); +$config->product->form->batchEdit['PO'] = array('type' => 'array', 'control' => 'select', 'required' => false, 'default' => '', 'options' => array()); +$config->product->form->batchEdit['QD'] = array('type' => 'array', 'control' => 'select', 'required' => false, 'default' => '', 'options' => array()); +$config->product->form->batchEdit['RD'] = array('type' => 'array', 'control' => 'select', 'required' => false, 'default' => '', 'options' => array()); +$config->product->form->batchEdit['type'] = array('type' => 'array', 'control' => 'select', 'required' => false, 'default' => 'normal', 'options' => $lang->product->typeList); +$config->product->form->batchEdit['status'] = array('type' => 'array', 'control' => 'select', 'required' => false, 'default' => 'normal', 'options' => $lang->product->statusList); +$config->product->form->batchEdit['desc'] = array('type' => 'array', 'control' => 'textarea', 'required' => false, 'default' => ''); +$config->product->form->batchEdit['acl'] = array('type' => 'array', 'control' => 'radio', 'required' => false, 'default' => 'private', 'options' => $lang->product->aclList); +if($config->systemMode != 'ALM') unset($config->product->form->batchEdit['program'], $config->product->form->batchEdit['line']); diff --git a/module/product/control.php b/module/product/control.php index 15962a371e..35239d65b9 100755 --- a/module/product/control.php +++ b/module/product/control.php @@ -16,8 +16,8 @@ class product extends control /** * Construct function. * - * @param moduleName - * @param methodName + * @param string moduleName + * @param string methodName * @access public * @return void */ @@ -390,8 +390,9 @@ class product extends control if(!empty($_POST)) { - $data = form::data($this->config->product->form->create); - $product = $this->productZen->prepareCreateExtras($data, $this->post->acl, $this->post->uid); + $formConfig = $this->productZen->appendFlowFields($this->config->product->form->create); + $data = form::data($formConfig); + $product = $this->productZen->prepareCreateExtras($data, $this->post->acl, $this->post->uid); $productID = $this->product->create($product, $this->post->uid, zget($_POST, 'lineName', '')); if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); @@ -421,8 +422,9 @@ class product extends control if(!empty($_POST)) { - $data = form::data($this->config->product->form->edit); - $product = $this->productZen->prepareEditExtras($data, $this->post->acl, $this->post->uid); + $formConfig = $this->productZen->appendFlowFields($this->config->product->form->edit); + $data = form::data($formConfig); + $product = $this->productZen->prepareEditExtras($data, $this->post->acl, $this->post->uid); $changes = $this->product->update($productID, $product, $this->post->uid); if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); @@ -441,17 +443,37 @@ class product extends control } /** + * 根据POST过来的ID列表,批量编辑相应产品。 * Batch edit products. * - * @param int $programID + * @param string $programID * @access public * @return void */ - public function batchEdit($programID = 0) + public function batchEdit(string $programID = '0') { - if($this->post->names) + $programID = (int)$programID; + + $_POST['productIdList'][1] = 1; + $_POST['name'][1] = '公司企业网站建设'; + $_POST['PO'][1] = 'productManager'; + $_POST['QD'][1] = 'testManager'; + $_POST['RD'][1] = 'productManager'; + $_POST['type'][1] = 'normal'; + $_POST['status'][1] = 'normal'; + $_POST['desc'][1] = '建立公司企业网站,可以更好对外展示。
'; + $_POST['acl'][1] = 'open'; + + if($this->post->name) { - $allChanges = $this->product->batchUpdate(); + $formConfig = $this->productZen->appendFlowFields($this->config->product->form->batchEdit, 'batch'); + $data = form::data($formConfig); + $products = $this->productZen->prepareBatchEditExtras($data); + if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); + + $allChanges = $this->product->batchUpdate($products); + + if(!empty($allChanges)) { foreach($allChanges as $productID => $changes) @@ -467,76 +489,16 @@ class product extends control return print(js::locate($locate, 'parent')); } - $productIDList = $this->post->productIDList; - if(empty($productIDList)) return print(js::locate($this->session->productList, 'parent')); + /* 获取要修改的产品ID列表。*/ + $productIdList = $this->post->productIDList; + $productIdList = '79,40,68'; + if(empty($productIdList)) return print(js::locate($this->session->productList, 'parent')); /* Set menu when page come from program. */ if($this->app->tab == 'program') $this->loadModel('program')->setMenu(0); - /* Set custom. */ - foreach(explode(',', $this->config->product->customBatchEditFields) as $field) $customFields[$field] = $this->lang->product->$field; - $this->view->customFields = $customFields; - $this->view->showFields = $this->config->product->custom->batchEditFields; - - $products = $this->dao->select('*')->from(TABLE_PRODUCT)->where('id')->in($productIDList)->fetchAll('id'); - $appendPoUsers = $appendQdUsers = $appendRdUsers = array(); - foreach($products as $product) - { - $appendPoUsers[$product->PO] = $product->PO; - $appendQdUsers[$product->QD] = $product->QD; - $appendRdUsers[$product->RD] = $product->RD; - } - - $this->loadModel('user'); - $poUsers = $this->user->getPairs('nodeleted|noclosed|pofirst', $appendPoUsers); - if(!empty($this->config->user->moreLink)) $this->config->moreLinks["PO"] = $this->config->user->moreLink; - - $qdUsers = $this->user->getPairs('nodeleted|noclosed|qdfirst', $appendQdUsers); - if(!empty($this->config->user->moreLink)) $this->config->moreLinks["QD"] = $this->config->user->moreLink; - - $rdUsers = $this->user->getPairs('nodeleted|noclosed|devfirst', $appendRdUsers); - if(!empty($this->config->user->moreLink)) $this->config->moreLinks["RD"] = $this->config->user->moreLink; - - $programs = array(); - $unauthorizedPrograms = array(); - if($this->config->systemMode == 'ALM') - { - $programs = $this->loadModel('program')->getTopPairs(); - - /* Get unauthorized programs. */ - $programIDList = array(); - foreach($products as $product) - { - if($product->program and !isset($programs[$product->program]) and !in_array($product->program, $programIDList)) $programIDList[] = $product->program; - } - $unauthorizedPrograms = $this->program->getPairsByList($programIDList); - - /* Get product lines by programs.*/ - $lines = array(0 => ''); - foreach($programs + $unauthorizedPrograms as $id => $program) - { - $lines[$id] = array('') + $this->product->getLinePairs($id); - } - } - else - { - $lines = array('') + $this->product->getLinePairs(); - } - - $this->view->title = $this->lang->product->batchEdit; - $this->view->position[] = $this->lang->product->batchEdit; - $this->view->lines = $lines; - $this->view->productIDList = $productIDList; - $this->view->products = $products; - $this->view->poUsers = $poUsers; - $this->view->qdUsers = $qdUsers; - $this->view->rdUsers = $rdUsers; - $this->view->programID = $programID; - $this->view->programs = array('' => '') + $programs; - $this->view->unauthorizedPrograms = $unauthorizedPrograms; - - unset($this->lang->product->typeList['']); - $this->display(); + /* 构造批量编辑页面表单配置数据。*/ + $this->productZen->buildBatchEditForm($programID, explode(',', $productIdList)); } /** @@ -875,7 +837,7 @@ class product extends control $this->product->buildProductSearchForm($param, $actionURL); /* Get product lines. */ - list($productLines, $programLines) = $this->getProductLines(); + list($productLines, $programLines) = $this->productZen->getProductLines(); $this->view->title = $this->lang->productCommon; $this->view->position[] = $this->lang->productCommon; diff --git a/module/product/model.php b/module/product/model.php index 9c5a1056fb..ef5ea8f484 100755 --- a/module/product/model.php +++ b/module/product/model.php @@ -206,13 +206,13 @@ class productModel extends model /** * Get by idList. * - * @param array $productIDList + * @param array $productIdList * @access public * @return array */ - public function getByIdList($productIDList) + public function getByIdList(array $productIdList): array { - return $this->dao->select('*')->from(TABLE_PRODUCT)->where('id')->in($productIDList)->fetchAll('id'); + return $this->dao->select('*')->from(TABLE_PRODUCT)->where('id')->in($productIdList)->fetchAll('id'); } /** @@ -658,14 +658,8 @@ class productModel extends model $oldProduct = $this->dao->findById($productID)->from(TABLE_PRODUCT)->fetch(); $this->lang->error->unique = $this->lang->error->repeat; - $this->dao->update(TABLE_PRODUCT)->data($product, 'uid,changeProjects,contactListMenu')->autoCheck() - ->checkIF(!empty($product->name), 'name', 'unique', "id != {$productID} and `program` = {$product->program} and `deleted` = '0'") - ->checkIF(!empty($product->code), 'code', 'unique', "id != {$productID} and `deleted` = '0'") - ->checkFlow() - ->where('id')->eq($productID) - ->exec(); - - if(dao::isError()) return false; + $result = $this->productTao->doUpdate($product, $productID, (int)$product->program); + if(!$result) return false; /* Update objectID field of file recode, that upload by editor. */ $this->loadModel('file')->updateObjectID($uid, $productID, 'product'); @@ -681,77 +675,50 @@ class productModel extends model } /** + * 批量更新产品信息。 * Batch update products. * + * @param array $products * @access public * @return array */ - public function batchUpdate() + public function batchUpdate(array $products): array { - $products = array(); - $allChanges = array(); - $data = fixer::input('post')->get(); - $oldProducts = $this->getByIdList($this->post->productIDList); - $nameList = array(); + if(empty($products)) return array(); - $extendFields = $this->getFlowExtendFields(); - foreach($data->productIDList as $productID) - { - $productName = $data->names[$productID]; + /* 初始化变量。*/ + $oldProducts = $this->getByIdList(array_keys($products)); + $allChanges = array(); + $updateViewProducts = array(); + $unlinkProducts = array(); + $linkProducts = array(); - $productID = (int)$productID; - $products[$productID] = new stdClass(); - if($this->config->systemMode == 'ALM' and isset($data->programs[$productID])) $products[$productID]->program = (int)$data->programs[$productID]; - if($this->config->systemMode == 'ALM' and isset($data->lines[$productID])) $products[$productID]->line = (int)$data->lines[$productID]; - $products[$productID]->name = $productName; - $products[$productID]->PO = $data->POs[$productID]; - $products[$productID]->QD = $data->QDs[$productID]; - $products[$productID]->RD = $data->RDs[$productID]; - $products[$productID]->type = $data->types[$productID]; - $products[$productID]->status = $data->statuses[$productID]; - $products[$productID]->desc = strip_tags($this->post->descs[$productID], $this->config->allowedTags); - $products[$productID]->acl = $data->acls[$productID]; - $products[$productID]->id = $productID; - - foreach($extendFields as $extendField) - { - $products[$productID]->{$extendField->field} = $this->post->{$extendField->field}[$productID]; - if(is_array($products[$productID]->{$extendField->field})) $products[$productID]->{$extendField->field} = join(',', $products[$productID]->{$extendField->field}); - - $products[$productID]->{$extendField->field} = htmlSpecialString($products[$productID]->{$extendField->field}); - } - } - if(dao::isError()) return print(js::error(dao::getError())); - - $unlinkProducts = array(); - $linkProducts = array(); + /* 根据产品ID,循环更新的产品信息. */ + $this->loadModel('personnel'); $this->lang->error->unique = $this->lang->error->repeat; foreach($products as $productID => $product) { $oldProduct = $oldProducts[$productID]; - if($this->config->systemMode == 'ALM') $programID = !isset($product->program) ? $oldProduct->program : (empty($product->program) ? 0 : $product->program); + if($this->config->systemMode == 'ALM') $programID = (int)zget($product, 'program', $oldProduct->program); - $this->dao->update(TABLE_PRODUCT) - ->data($product) - ->autoCheck() - ->batchCheck($this->config->product->edit->requiredFields , 'notempty') - ->checkIF((!empty($product->name) and $this->config->systemMode == 'ALM'), 'name', 'unique', "id != $productID and `program` = $programID and `deleted` = '0'") - ->checkFlow() - ->where('id')->eq($productID) - ->exec(); - if(dao::isError()) return print(js::error('product#' . $productID . dao::getError(true))); + $result = $this->productTao->doUpdate($product, $productID, $programID); + if(!$result) return array('result' => 'fail', 'message' => 'product#' . $productID . dao::getError(true)); - /* When acl is open, white list set empty. When acl is private,update user view. */ - if($product->acl == 'open') $this->loadModel('personnel')->updateWhitelist(array(), 'product', $productID); - if($product->acl != 'open') $this->loadModel('user')->updateUserView($productID, 'product'); - if($product->type == 'normal' and $oldProduct->type != 'normal') $unlinkProducts[] = $productID; - if($product->type != 'normal' and $oldProduct->type == 'normal') $linkProducts[] = $productID; + /* When acl is open, white list set empty. */ + if($product->acl == 'open') $this->personnel->updateWhitelist(array(), 'product', $productID); + + /* 如果产品类型或权限有修改,则标记该产品,用做后面处理。*/ + if($oldProduct->acl != $product->acl and $product->acl != 'open') $updateViewProducts[] = $productID; + if($product->type == 'normal' and $oldProduct->type != 'normal') $unlinkProducts[] = $productID; + if($product->type != 'normal' and $oldProduct->type == 'normal') $linkProducts[] = $productID; $allChanges[$productID] = common::createChanges($oldProduct, $product); } - if(!empty($unlinkProducts)) $this->loadModel('branch')->unlinkBranch4Project($unlinkProducts); - if(!empty($linkProducts)) $this->loadModel('branch')->linkBranch4Project($linkProducts); + /* 对标记的产品,批量做对应的后续处理。*/ + if(!empty($updateViewProducts)) $this->loadModel('user')->updateUserView($updateViewProducts, 'product'); + if(!empty($unlinkProducts)) $this->loadModel('branch')->unlinkBranch4Project($unlinkProducts); + if(!empty($linkProducts)) $this->loadModel('branch')->linkBranch4Project($linkProducts); return $allChanges; } @@ -1729,12 +1696,19 @@ class productModel extends model /* * Get all lines. + * + * @param array $programIdList * @access public * @return array */ - public function getLines() + public function getLines(array $programIdList = array()): array { - return $this->dao->select('*')->from(TABLE_MODULE)->where('type')->eq('line')->andWhere('deleted')->eq(0)->orderBy('`order`')->fetchAll(); + return $this->dao->select('*')->from(TABLE_MODULE) + ->where('type')->eq('line') + ->beginIF($programIdList)->andWhere('root')->in($programIdList)->fi() + ->andWhere('deleted')->eq(0) + ->orderBy('`order`') + ->fetchAll(); } /** diff --git a/module/product/tao.php b/module/product/tao.php index bb04178ba9..ecf050d6ac 100644 --- a/module/product/tao.php +++ b/module/product/tao.php @@ -562,15 +562,27 @@ class productTao extends productModel } /** - * 从module表中查询所有产品线。 - * Get all product lines from module table. - * TODO move to the MODULE module. + * 执行SQL,更新到对应的产品信息。 + * Do update this product. * + * @param object $product + * @param int $productID + * @param int $programID * @access protected - * @return array + * @return bool */ - protected function getProductLinesTODO(): array + protected function doUpdate(object $product, int $productID, int $programID): bool { - return $this->dao->select('*')->from(TABLE_MODULE)->where('type')->eq('line')->andWhere('deleted')->eq(0)->orderBy('`order` asc')->fetchAll(); + if(empty($productID)) return false; + if(count(get_object_vars($product)) == 0) return false; + + $this->dao->update(TABLE_PRODUCT)->data($product)->autoCheck() + ->checkIF(!empty($product->name), 'name', 'unique', "id != {$productID} and `program` = {$programID} and `deleted` = '0'") + ->checkIF(!empty($product->code), 'code', 'unique', "id != {$productID} and `deleted` = '0'") + ->checkFlow() + ->where('id')->eq($productID) + ->exec(); + + return !dao::isError(); } } diff --git a/module/product/test/model/batchupdate.php b/module/product/test/model/batchupdate.php index f2e98c1c55..0875a62ed9 100755 --- a/module/product/test/model/batchupdate.php +++ b/module/product/test/model/batchupdate.php @@ -3,28 +3,53 @@ include dirname(__FILE__, 5) . "/test/lib/init.php"; include dirname(__FILE__, 2) . '/product.class.php'; +zdTable('product')->gen(10); + /** title=测试 productModel->batchUpdate(); cid=1 pid=1 -批量修改product名称 >> name,正常产品1,批量修改产品1 -批量修改produc测试负责人 >> QD,test2,test1 -批量修改product状态 >> status,normal,closed - */ -$names = array('1' => '批量修改产品1', '2' => '正常产品2', '3' => '正常产品3'); -$QDs = array('1' => 'test1', '2' => 'test1', '3' => 'test3'); -$status = array('1' => 'normal', '2' => 'normal', '3' => 'closed'); - -$changeName = array('names' => $names); -$changeQDs = array('QDs' => $QDs); -$changeStatus = array('statuses' => $status); - $product = new productTest('admin'); -r($product->batchUpdateTest($changeName, '1')) && p('0:field,old,new') && e('name,正常产品1,批量修改产品1'); // 批量修改product名称 -r($product->batchUpdateTest($changeQDs, '2')) && p('0:field,old,new') && e('QD,test2,test1'); // 批量修改produc测试负责人 -r($product->batchUpdateTest($changeStatus, '3')) && p('0:field,old,new') && e('status,normal,closed'); // 批量修改product状态 +$products = array(); +$products[1] = new stdclass(); +$products[1]->program = 1; +$products[1]->name = '批量修改产品1'; +$products[1]->type = 'branch'; +$products[1]->acl = 'private'; + +$changes = $product->batchUpdateTest($products); +$changes = $changes[1]; +r(array_shift($changes)) && p('field,old,new') && e('program,0,1'); +r(array_shift($changes)) && p('field,old,new') && e('name,正常产品1,批量修改产品1'); +r(array_shift($changes)) && p('field,old,new') && e('type,normal,branch'); +r(array_shift($changes)) && p('field,old,new') && e('acl,open,private'); + +$products = array(); +$products[2] = new stdclass(); +$products[2]->program = 1; +$products[2]->name = '批量修改产品1'; +$products[2]->type = 'branch'; +$products[2]->acl = 'private'; + +$changes = $product->batchUpdateTest($products); +$changes['message'] = str_replace('product#', 'product:', $changes['message']); +r($changes) && p('result,message') && e('fail,product:2『产品名称』已经有『批量修改产品1』这条记录了。\n'); //验证唯一。 + +r($product->batchUpdateTest(array())) && p() && e('0'); //不传任何数据。 + +$products = array(); +$products[1] = new stdclass(); +$products[1]->program = 1; +$products[1]->name = '批量修改产品1'; +$products[1]->type = 'normal'; +$products[1]->acl = 'open'; + +$changes = $product->batchUpdateTest($products); +$changes = $changes[1]; +r(array_shift($changes)) && p('field,old,new') && e('type,branch,normal'); +r(array_shift($changes)) && p('field,old,new') && e('acl,private,open'); diff --git a/module/product/test/product.class.php b/module/product/test/product.class.php index ac8734e644..085c6364cc 100644 --- a/module/product/test/product.class.php +++ b/module/product/test/product.class.php @@ -564,40 +564,31 @@ class productTest /** * Test batch update products. * - * @param array $param - * @param int $productID + * @param array $products * @access public - * @return void + * @return array */ - public function batchUpdateTest($param, $productID) + public function batchUpdateTest(array $products): array { - $batchUpdateFields['productIDList'] = array('1' => '1', '2' => '2', '3' => '3'); - $batchUpdateFields['programs'] = array('1' => '', '2' => '1', '3' => '2'); - $batchUpdateFields['names'] = array('1' => '正常产品1', '2' => '正常产品2', '3' => '正常产品3'); - $batchUpdateFields['lines'] = array('1' => '0', '2' => '1', '3' => '2'); - $batchUpdateFields['POs'] = array('1' => 'po1', '2' => 'po2', '3' => 'po3'); - $batchUpdateFields['QDs'] = array('1' => 'test1', '2' => 'test2', '3' => 'test3'); - $batchUpdateFields['RDs'] = array('1' => 'dev1', '2' => 'dev2', '3' => 'dev3'); - $batchUpdateFields['types'] = array('1' => 'normal', '2' => 'normal', '3' => 'normal'); - $batchUpdateFields['statuses'] = array('1' => 'normal', '2' => 'normal', '3' => 'normal'); - $batchUpdateFields['descs'] = array('1' => '<div> <p><h1>一、禅道项目管理软件是做什么的?</h1> 禅道由 青岛易软天创网络科技有限公司开发,国产开源项目管理软件。它集产品管理、项目管理、质量管理、文档管理、组织管理和事务管理于一体,是一款专业的研发项目管理软件,完整覆盖了研发项目管理的核心流程。禅道管理思想注重实效,功能完备丰富,操作简洁高效,界面美观大方,搜索功能强大,统计报表丰富多样,软件架构合理,扩展灵活,有完善的API可以调用。禅道,专注研发项目管理 </p> <p>我是数字符号23@#$%#^$ </p> <p>我是英文dashcuscbrewg </p> </div>', '2' => '<div> <p><h1>一、禅道项目管理软件是做什么的?</h1> 禅道由 青岛易软天创网络科技有限公司开发,国产开源项目管理软件。它集产品管理、项目管理、质量管理、文档管理、组织管理和事务管理于一体,是一款专业的研发项目管理软件,完整覆盖了研发项目管理的核心流程。禅道管理思想注重实效,功能完备丰富,操作简洁高效,界面美观大方,搜索功能强大,统计报表丰富多样,软件架构合理,扩展灵活,有完善的API可以调用。禅道,专注研发项目管理 </p> <p>我是数字符号23@#$%#^$ </p> <p>我是英文dashcuscbrewg </p> </div>', '3' => 'i<div> <p><h1>一、禅道项目管理软件是做什么的?</h1> 禅道由 青岛易软天创网络科技有限公司开发,国产开源项目管理软件。它集产品管理、项目管理、质量管理、文档管理、组织管理和事务管理于一体,是一款专业的研发项目管理软件,完整覆盖了研发项目管理的核心流程。禅道管理思想注重实效,功能完备丰富,操作简洁高效,界面美观大方,搜索功能强大,统计报表丰富多样,软件架构合理,扩展灵活,有完善的API可以调用。禅道,专注研发项目管理 </p> <p>我是数字符号23@#$%#^$ </p> <p>我是英文dashcuscbrewg </p> </div>'); - $batchUpdateFields['acls'] = array('1' => 'open', '2' => 'open', '3' => 'open'); + return $this->objectModel->batchUpdate($products); + } - foreach($batchUpdateFields as $field => $defaultValue) $_POST[$field] = $defaultValue; + /** + * 测试doUpdate方法 + * Test doUpdate method + * + * @param object $product + * @param int $productID + * @param int $programID + * @access public + * @return object|array|null + */ + public function doUpdateTest(object $product, int $productID, int $programID): object|array|false + { + $this->objectModel->doUpdate($product, $productID, $programID); - foreach($param as $key => $value) $_POST[$key] = $value; - - $changes = $this->objectModel->batchUpdate(); - unset($_POST); - - if(dao::isError()) - { - return dao::getError(); - } - else - { - return $changes[$productID]; - } + if(dao::isError()) return dao::getError(); + return $this->objectModel->getById($productID); } /** diff --git a/module/product/test/tao/doupdate.php b/module/product/test/tao/doupdate.php new file mode 100755 index 0000000000..670fd729bf --- /dev/null +++ b/module/product/test/tao/doupdate.php @@ -0,0 +1,51 @@ +#!/usr/bin/env php +gen(10); + +/** + +title=测试productModel->fetchPairs(); +cid=1 +pid=1 + +*/ + +$product = new productTest('admin'); + +$productID = 1; +$programID = 0; +$data = new stdclass(); +$data->program = 1; +$data->name = '修改产品1'; +$data->code = 'newcode1'; +$data->type = 'branch'; +$data->acl = 'private'; + +r($product->doUpdateTest($data, $productID, $programID)) && p('program,name,code,type,acl') && e('1,修改产品1,newcode1,branch,private'); + +$data->name = '修改产品2'; +$data->code = 'newcode2'; +r($product->doUpdateTest($data, 0, 1)) && p() && e('0'); +r($product->doUpdateTest($data, 0, 0)) && p() && e('0'); +r($product->doUpdateTest($data, 0, -1)) && p() && e('0'); +r($product->doUpdateTest($data, -1, 1)) && p() && e('0'); +r($product->doUpdateTest($data, -1, 0)) && p() && e('0'); +r($product->doUpdateTest($data, -1, -1)) && p() && e('0'); +r($product->doUpdateTest(new stdclass(), 3, 2)) && p('name,code') && e('正常产品3,code3'); + +$productID = 2; +$programID = 1; +$data = new stdclass(); +$data->program = 1; +$data->name = '修改产品1'; +$data->code = 'newcode1'; +$data->type = 'branch'; +$data->acl = 'private'; +$result = $product->doUpdateTest($data, $productID, $programID); + +r(array_shift($result['name'])) && p() && e('『产品名称』已经有『修改产品1』这条记录了。如果您确定该记录已删除,请到后台-系统-数据-回收站还原。'); +r(array_shift($result['code'])) && p() && e('『产品代号』已经有『newcode1』这条记录了。如果您确定该记录已删除,请到后台-系统-数据-回收站还原。'); + diff --git a/module/product/view/batchedit.html.php b/module/product/view/batchedit.html.php index 9f2efa2e6a..a2e8987737 100755 --- a/module/product/view/batchedit.html.php +++ b/module/product/view/batchedit.html.php @@ -73,19 +73,19 @@ - + config->moreLinks["PO"])) $this->config->moreLinks["POs[$productID]"] = $this->config->moreLinks["PO"]; if(!empty($this->config->moreLinks["QD"])) $this->config->moreLinks["QDs[$productID]"] = $this->config->moreLinks["QD"]; if(!empty($this->config->moreLinks["RD"])) $this->config->moreLinks["RDs[$productID]"] = $this->config->moreLinks["RD"]; ?> - + config->systemMode == 'ALM'):?> - program])):?> - ' style='overflow:visible'>program, "class='form-control' disabled");?> + program])):?> + ' style='overflow:visible'>program, "class='form-control' disabled");?> - ' style='overflow:visible'>program, "class='form-control picker-select' onchange='loadProductLines(this.value, $productID)'");?> + ' style='overflow:visible'>program, "class='form-control picker-select' onchange='loadProductLines(this.value, $productID)'");?> name, "class='form-control'");?> diff --git a/module/product/zen.php b/module/product/zen.php index 88988a4c58..1eb84f96c3 100644 --- a/module/product/zen.php +++ b/module/product/zen.php @@ -201,28 +201,29 @@ class productZen extends product */ private function getFormFields4Create(int $programID = 0, array $fields = array()): array { - if(empty($fields)) $fields = $this->config->product->form->create; + if(empty($fields)) $fields = $this->appendFlowFields($this->config->product->form->create); + /* 准备数据。*/ $this->loadModel('user'); $poUsers = $this->user->getPairs('nodeleted|pofirst|noclosed'); $qdUsers = $this->user->getPairs('nodeleted|qdfirst|noclosed'); $rdUsers = $this->user->getPairs('nodeleted|devfirst|noclosed'); $users = $this->user->getPairs('nodeleted|noclosed'); + /* 追加字段的name、title属性,展开user数据。 */ foreach($fields as $field => $attr) { if(isset($attr['options']) and $attr['options'] == 'users') $fields[$field]['options'] = $users; - $fields[$field]['name'] = $field; - $fields[$field]['title'] = zget($this->lang->product, $field); + if(!isset($fields[$field]['name'])) $fields[$field]['name'] = $field; + if(!isset($fields[$field]['title'])) $fields[$field]['title'] = zget($this->lang->product, $field); } - $fields['program']['options'] = array('') + $this->loadModel('program')->getTopPairs('', 'noclosed'); - $fields['PO']['options'] = $poUsers; - $fields['QD']['options'] = $qdUsers; - $fields['RD']['options'] = $rdUsers; - - if($programID) $fields['line']['options'] = array('') + $this->product->getLinePairs($programID); - if(empty($programID) or $this->config->systemMode != 'ALM') unset($fields['line'], $fields['lineName']); + /* 设置下拉菜单内容。 */ + $fields['PO']['options'] = $poUsers; + $fields['QD']['options'] = $qdUsers; + $fields['RD']['options'] = $rdUsers; + if(isset($fields['program'])) $fields['program']['options'] = array('') + $this->loadModel('program')->getTopPairs('', 'noclosed'); + if($programID and isset($fields['line'])) $fields['line']['options'] = array('') + $this->product->getLinePairs($programID); return $fields; } @@ -239,12 +240,13 @@ class productZen extends product { /* Init fields. */ $programID = (int)$product->program; - $fields = $this->getFormFields4Create($programID, $this->config->product->form->edit); + $fields = $this->getFormFields4Create($programID, $this->appendFlowFields($this->config->product->form->edit)); + $fields['changeProjects'] = array('type' => 'string', 'control' => 'hidden', 'required' => false, 'default' => ''); /* Check program priv, and append to program list that is not exist product's program. */ $hasPrivPrograms = $this->app->user->view->programs; if($programID and strpos(",{$hasPrivPrograms},", ",{$programID},") === false) $fields['program']['control'] = 'hidden'; - if(!isset($fields['program']['options'][$programID]) and $programID) + if(isset($fields['program']) and !isset($fields['program']['options'][$programID]) and $programID) { $program = $this->program->getByID($programID); $fields['program']['options'] += array($programID => $program->name); @@ -255,7 +257,28 @@ class productZen extends product { if(isset($product->{$field})) $fields[$field]['default'] = $product->{$field}; } - $fields['changeProjects'] = array('type' => 'string', 'control' => 'hidden', 'required' => false, 'default' => ''); + + return $fields; + } + + /** + * 获取批量编辑产品页面的表单配置。 + * Get form fields config for batch edit page. + * + * @access private + * @return array + */ + private function getFormFields4BatchEdit(): array + { + /* Init fields. */ + $fields = $this->getFormFields4Create(0, $this->appendFlowFields($this->config->product->form->batchEdit, 'batch')); + + /* Remove not show fields. */ + $showFields = explode(',', $this->config->product->custom->batchEditFields); + foreach($fields as $field => $attr) + { + if(!in_array($field, $showFields) and !$attr['required']) unset($fields[$field]); + } return $fields; } @@ -263,23 +286,45 @@ class productZen extends product /** * Get product lines and product lines of program. * + * @param array $programIdList + * @param string $params hasempty * @access protected * @return array */ - protected function getProductLines(): array + protected function getProductLines(array $programIdList = array(), string $params = ''): array { /* Get all product lines. */ - $productLines = $this->product->getProductLinesTODO(); + $productLines = $this->product->getLines($programIdList); /* Collect product lines of program lines. */ - $programLines = array(); - foreach($productLines as $productLine) - { - if(!isset($programLines[$productLine->root])) $programLines[$productLine->root] = array(); - $programLines[$productLine->root][$productLine->id] = $productLine->name; - } + $initArray = strpos($params, 'hasempty') !== false ? array('') : array(); + $linePairs = $initArray; + foreach($programIdList as $programID) $linePairs[$programID] = $initArray; + foreach($productLines as $line) $linePairs[$programID][$line->id] = $line->name; - return array($productLines, $programLines); + return array($productLines, $linePairs); + } + + /** + * 根据产品和已授权项目集,获取关联产品的未授权项目集。 + * Get unauthorized programs by products and authorized programs. + * + * @param array $products + * @param array $authPrograms + * @access private + * @return array + */ + private function getUnauthProductPrograms(array $products, array $authPrograms): array + { + $unauthPrograms = array(); + $programIdList = array(); + foreach($products as $product) + { + if($product->program and !isset($authPrograms[$product->program])) $programIdList[$product->program] = $product->program; + } + if($programIdList) $unauthPrograms = $this->program->getPairsByList($programIdList); + + return $unauthPrograms; } /** @@ -378,6 +423,45 @@ class productZen extends product $this->display(); } + /** + * 构建批量编辑产品页面数据。 + * Build form for batch edit page + * + * @param int $programID + * @param array $productIdList + * @access protected + * @return void + */ + protected function buildBatchEditForm(int $programID, array $productIdList) + { + $products = $this->product->getByIdList($productIdList); + + /* 获取项目集和产品线,并将项目集分成授权和非授权。以便view的下拉菜单使用对应的数据。 */ + $authPrograms = array(); + $unauthPrograms = array(); + $lines = array(); + if($this->config->systemMode == 'ALM') + { + $authPrograms = $this->loadModel('program')->getTopPairs(); + $unauthPrograms = $this->getUnauthProductPrograms($products, $authPrograms); + + /* Get product lines by programs.*/ + $programIdList = array_merge(array_keys($authPrograms), array_keys($unauthPrograms)); + list(, $lines) = $this->getProductLines($programIdList); + } + + /* 给view层赋值。 */ + $this->view->title = $this->lang->product->batchEdit; + $this->view->lines = $lines; + $this->view->products = $products; + $this->view->programID = $programID; + $this->view->authPrograms = array('' => '') + $authPrograms; + $this->view->unauthPrograms = $unauthPrograms; + + unset($this->lang->product->typeList['']); + $this->display(); + } + /** * 为view层设置要用数据。 * Set view variables and display for project page. @@ -448,13 +532,56 @@ class productZen extends product */ protected function prepareEditExtras(form $data, string $acl, string $uid = ''): object { - $product = fixer::input('post')->setIF($acl == 'open', 'whitelist', '') + $product = $data->setIF($acl == 'open', 'whitelist', '') ->stripTags($this->config->product->editor->edit['id'], $this->config->allowedTags) ->get(); return $this->loadModel('file')->processImgURL($product, $this->config->product->editor->edit['id'], $uid); } + /** + * 预处理批量编辑产品数据,将按字段为分组数据重组为产品分组的数据。 + * Prepare batch edit extras. + * + * @param form $data + * @access protected + * @return array + */ + protected function prepareBatchEditExtras(form $data): array + { + $formConfig = $data->rawconfig; + $editForm = $this->config->product->form->edit; + $data = $data->get(); + $products = array(); + + /* 将按字段为分组数据重组为产品分组的数据。*/ + foreach($data->name as $productID => $productName) + { + $productID = (int)$productID; + + /* 根据表单配置构造产品数据。*/ + $product = new stdClass(); + $product->id = $productID; + foreach($formConfig as $field => $attr) + { + /* 获取对应的字段数据。*/ + $product->{$field} = zget($data->{$field}, $productID, $attr['default']); + + /* 根据配置规则,格式化字段数据。 */ + if(isset($editForm[$field]) and $editForm[$field]['type'] == 'int') $product->{$field} = (int)$product->{$field}; + if(!empty($attr['filter']) and $attr['filter'] == 'trim') $product->{$field} = trim($product->{$field}); + if(is_array($product->$field)) $product->{$field} = implode(',', $product->{$field}); + + /* 检查必填项。 */ + if(!empty($attr['required']) and validater::checkEmpty($product->{$field})) dao::$errors[] = 'product #' . $productID . sprintf($this->lang->error->notempty, zget($attr, 'title', zget($this->lang->product, $field))); + } + + $products[$productID] = $product; + } + + return $products; + } + /** * 成功插入产品数据后,其他的额外操作。 * Process after create product. @@ -596,4 +723,37 @@ class productZen extends product return $data; } + + /** + * 追加工作流配置字段。 + * Append flow fields. + * + * @param array $fields + * @param string $type single|batch + * @access protected + * @return array + */ + protected function appendFlowFields(array $fields, string $type = 'single'): array + { + $extendFields = $this->product->getFlowExtendFields(); + if(empty($extendFields)) return $fields; + + /* 构造表单属性,并追加到表单配置中。 */ + foreach($extendFields as $extendField) + { + $control = $extendField->control; + if($control == 'richtext') $control = 'editor'; + if($control == 'input') $control = 'text'; + + $field = $extendField; + $fields[$field] = array(); + $fields[$field]['type'] = $type == 'single' ? 'string' : 'array'; + $fields[$field]['control'] = $control; + $fields[$field]['title'] = $extendField->name; + $fields[$field]['default'] = $extendField->default; + $fields[$field]['options'] = $extendField->options; + } + + return $fields; + } } diff --git a/test/data/todo.yaml b/test/data/todo.yaml index 5fa53b09c3..72098c82be 100644 --- a/test/data/todo.yaml +++ b/test/data/todo.yaml @@ -136,13 +136,13 @@ fields: postfix: "" loop: 0 format: "" - - field: finishedDate - note: "完成时间" - range: "" - prefix: "" - postfix: "" - loop: 0 - format: "" + #- field: finishedDate + # note: "完成时间" + # range: "" + # prefix: "" + # postfix: "" + # loop: 0 + # format: "" - field: closedBy note: "由谁关闭" range: "" @@ -150,6 +150,6 @@ fields: postfix: "" loop: 0 format: "" - - field: closedDate - note: "关闭时间" - range: "" + #- field: closedDate + # note: "关闭时间" + # range: ""