feat(产品需求): 产品需求相关代码
This commit is contained in:
@@ -43,10 +43,14 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode REQUIREMENT_STATUS_NOT_ALLOW_CLOSE = new ErrorCode(1_008_002_005, "只有已验收的需求才能关闭");
|
||||
ErrorCode REQUIREMENT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_008_002_006, "需求状态定义不存在或已停用");
|
||||
ErrorCode REQUIREMENT_PARENT_NOT_ALLOW_SPLIT = new ErrorCode(1_008_002_007, "父需求状态不是待分流或实施中,不允许拆分");
|
||||
ErrorCode REQUIREMENT_CHILD_NOT_ALLOW_CLOSE = new ErrorCode(1_008_002_008, "存在子需求未处于可关闭状态,请先处理子需求");
|
||||
ErrorCode REQUIREMENT_CHILD_NOT_ALLOW_CLOSE = new ErrorCode(1_008_002_008, "存在子需求状态不对,请先处理子需求");
|
||||
ErrorCode REQUIREMENT_MODULE_NOT_EXISTS = new ErrorCode(1_008_002_009, "需求模块不存在");
|
||||
ErrorCode REQUIREMENT_MODULE_NAME_DUPLICATE = new ErrorCode(1_008_002_010, "已经存在名称为【{}】的模块");
|
||||
ErrorCode REQUIREMENT_MODULE_NOT_BELONG_TO_PRODUCT = new ErrorCode(1_008_002_011, "模块不属于当前产品");
|
||||
ErrorCode REQUIREMENT_MODULE_HAS_NON_TERMINAL_REQUIREMENTS = new ErrorCode(1_008_002_012, "模块下存在非终态需求,不可删除");
|
||||
ErrorCode REQUIREMENT_HAS_CHILDREN = new ErrorCode(1_008_002_013, "存在子需求,请先删除子需求");
|
||||
ErrorCode REQUIREMENT_STATUS_NOT_ALLOW_DELETE = new ErrorCode(1_008_002_014, "只有待确认、待评审、待分流状态的需求才能删除");
|
||||
ErrorCode REQUIREMENT_MODULE_HAS_CHILDREN = new ErrorCode(1_008_002_015, "存在子模块,请先删除子模块");
|
||||
ErrorCode REQUIREMENT_MODULE_HAS_REQUIREMENTS = new ErrorCode(1_008_002_016, "模块下存在需求,请先删除需求");
|
||||
|
||||
}
|
||||
|
||||
@@ -2,17 +2,7 @@ package com.njcn.rdms.module.project.controller.admin.product;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.ProductRequirementCloseReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.ProductRequirementLifecycleRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.ProductRequirementModuleRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.ProductRequirementModuleSaveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.ProductRequirementPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.ProductRequirementRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.ProductRequirementSaveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.ProductRequirementSplitReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.ProductRequirementStatusActionReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.ProductRequirementStatusTransitionRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.ProductRequirementUpdateReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.*;
|
||||
import com.njcn.rdms.module.project.service.product.ProductRequirementService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -58,8 +48,8 @@ public class ProductRequirementController {
|
||||
@Parameter(name = "id", description = "需求编号", required = true, example = "1024")
|
||||
@Parameter(name = "productId", description = "产品编号", required = true, example = "1024")
|
||||
public CommonResult<ProductRequirementRespVO> getRequirement(@RequestParam("id") Long id,
|
||||
@RequestParam("productId") Long productId) {
|
||||
return success(requirementService.getRequirement(id));
|
||||
@RequestParam("productId") Long productId) {
|
||||
return success(requirementService.getRequirement(id, productId));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@@ -69,12 +59,9 @@ public class ProductRequirementController {
|
||||
}
|
||||
|
||||
@GetMapping("/tree")
|
||||
@Operation(summary = "获取需求树形列表")
|
||||
@Parameter(name = "productId", description = "产品编号", required = true, example = "1024")
|
||||
@Parameter(name = "moduleId", description = "模块编号", example = "1024")
|
||||
public CommonResult<List<ProductRequirementRespVO>> getRequirementTree(@RequestParam("productId") Long productId,
|
||||
@RequestParam(value = "moduleId", required = false) Long moduleId) {
|
||||
return success(requirementService.getRequirementTree(productId, moduleId));
|
||||
@Operation(summary = "获取需求树形列表(分页)")
|
||||
public CommonResult<PageResult<ProductRequirementRespVO>> getRequirementTree(@Valid ProductRequirementPageReqVO pageReqVO) {
|
||||
return success(requirementService.getRequirementTree(pageReqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/change-status")
|
||||
@@ -86,17 +73,16 @@ public class ProductRequirementController {
|
||||
|
||||
@PostMapping("/delete")
|
||||
@Operation(summary = "删除产品需求")
|
||||
@Parameter(name = "id", description = "需求编号", required = true, example = "1024")
|
||||
@Parameter(name = "productId", description = "产品编号", required = true, example = "1024")
|
||||
public CommonResult<Boolean> deleteRequirement(@RequestParam("id") Long id,
|
||||
@RequestParam("productId") Long productId) {
|
||||
requirementService.deleteRequirement(id, productId);
|
||||
public CommonResult<Boolean> deleteRequirement(@Valid @RequestBody ProductRequirementDeleteReqVO reqVO) {
|
||||
requirementService.deleteRequirement(reqVO.getId(), reqVO.getProductId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/split")
|
||||
@Operation(summary = "拆分产品需求")
|
||||
public CommonResult<Long> splitRequirement(@Valid @RequestBody ProductRequirementSplitReqVO reqVO) {
|
||||
System.out.println("-----------------------");
|
||||
System.out.println(reqVO);
|
||||
return success(requirementService.splitRequirement(reqVO));
|
||||
}
|
||||
|
||||
@@ -131,24 +117,21 @@ public class ProductRequirementController {
|
||||
|
||||
@PostMapping("/module/create")
|
||||
@Operation(summary = "创建需求模块")
|
||||
public CommonResult<Long> createRequirementModule(@Valid @RequestBody ProductRequirementModuleSaveReqVO reqVO) {
|
||||
public CommonResult<Long> createRequirementModule(@Valid @RequestBody ProductRequirementModuleReqVO reqVO) {
|
||||
return success(requirementService.createRequirementModule(reqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/module/update")
|
||||
@Operation(summary = "更新需求模块")
|
||||
public CommonResult<Boolean> updateRequirementModule(@Valid @RequestBody ProductRequirementModuleSaveReqVO reqVO) {
|
||||
public CommonResult<Boolean> updateRequirementModule(@Valid @RequestBody ProductRequirementModuleReqVO reqVO) {
|
||||
requirementService.updateRequirementModule(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/module/delete")
|
||||
@Operation(summary = "删除需求模块")
|
||||
@Parameter(name = "moduleId", description = "模块编号", required = true, example = "1024")
|
||||
@Parameter(name = "productId", description = "产品编号", required = true, example = "1024")
|
||||
public CommonResult<Boolean> deleteRequirementModule(@RequestParam("moduleId") Long moduleId,
|
||||
@RequestParam("productId") Long productId) {
|
||||
requirementService.deleteRequirementModule(moduleId, productId);
|
||||
public CommonResult<Boolean> deleteRequirementModule(@Valid @RequestBody ProductRequirementModuleDeleteReqVO reqVO) {
|
||||
requirementService.deleteRequirementModule(reqVO.getId(), reqVO.getProductId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 管理后台 - 产品需求删除 Request VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 产品需求删除 Request VO")
|
||||
@Data
|
||||
public class ProductRequirementDeleteReqVO {
|
||||
@Schema(description = "需求ID(编辑时传入)", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "产品ID不能为空")
|
||||
private Long productId;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 管理后台 - 产品需求模块删除 Request VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 产品需求模块删除 Request VO")
|
||||
@Data
|
||||
public class ProductRequirementModuleDeleteReqVO {
|
||||
@Schema(description = "模块ID(编辑时传入)", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "产品ID不能为空")
|
||||
private Long productId;
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import lombok.Data;
|
||||
*/
|
||||
@Schema(description = "管理后台 - 产品需求模块保存 Request VO")
|
||||
@Data
|
||||
public class ProductRequirementModuleSaveReqVO {
|
||||
public class ProductRequirementModuleReqVO {
|
||||
|
||||
@Schema(description = "模块ID(编辑时传入)", example = "1024")
|
||||
private Long id;
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.njcn.rdms.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 管理后台 - 产品需求分页 Request VO
|
||||
*/
|
||||
@@ -19,6 +22,9 @@ public class ProductRequirementPageReqVO extends PageParam {
|
||||
@Schema(description = "所属模块ID", example = "1024")
|
||||
private Long moduleId;
|
||||
|
||||
@Schema(description = "所属模块ID列表(包含子模块,用于IN查询)", example = "[1024, 1025]")
|
||||
private List<Long> moduleIds;
|
||||
|
||||
@Schema(description = "父需求ID(查询子需求时使用)", example = "1024")
|
||||
private Long parentId;
|
||||
|
||||
|
||||
@@ -23,6 +23,13 @@ public class ProductRequirementSplitReqVO {
|
||||
@NotNull(message = "产品编号不能为空")
|
||||
private Long productId;
|
||||
|
||||
@Schema(description = "所属模块ID(为空时归入全部需求)", example = "1024")
|
||||
private Long moduleId;
|
||||
|
||||
@Schema(description = "是否需要评审(0不需要;1需要)", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
@NotNull(message = "是否需要评审不能为空")
|
||||
private Integer reviewRequired;
|
||||
|
||||
@Schema(description = "需求标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "支持需求模块化管理")
|
||||
@NotBlank(message = "需求标题不能为空")
|
||||
@Size(max = 200, message = "需求标题长度不能超过200个字符")
|
||||
@@ -40,6 +47,10 @@ public class ProductRequirementSplitReqVO {
|
||||
@NotNull(message = "优先级不能为空")
|
||||
private Integer priority;
|
||||
|
||||
@Schema(description = "提出人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "提出人不能为空")
|
||||
private Long proposerId;
|
||||
|
||||
@Schema(description = "当前处理人用户编号", example = "1024")
|
||||
private Long currentHandlerUserId;
|
||||
|
||||
@@ -53,7 +64,4 @@ public class ProductRequirementSplitReqVO {
|
||||
@Schema(description = "排序值(越小越靠前)", example = "0")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "拆分原因", example = "需求过大,需要拆分实现")
|
||||
private String splitReason;
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ public class ProductRequirementStatusActionReqVO {
|
||||
@NotNull(message = "需求ID不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "产品编号不能为空")
|
||||
private Long productId;
|
||||
|
||||
@Schema(description = "动作编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "dispatch")
|
||||
@NotBlank(message = "动作编码不能为空")
|
||||
@Size(max = 32, message = "动作编码长度不能超过32个字符")
|
||||
@@ -28,5 +32,4 @@ public class ProductRequirementStatusActionReqVO {
|
||||
|
||||
@Schema(description = "实现项目编号(dispatch动作时可选)", example = "1024")
|
||||
private Long implementProjectId;
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ public interface ProductRequirementMapper extends BaseMapperX<ProductRequirement
|
||||
.eqIfPresent(ProductRequirementDO::getCurrentHandlerUserId, reqVO.getCurrentHandlerUserId())
|
||||
// 来源类型精确匹配
|
||||
.eqIfPresent(ProductRequirementDO::getSourceType, reqVO.getSourceType())
|
||||
// 模块ID精确匹配
|
||||
// 模块ID列表IN查询(优先使用moduleIds,用于支持子模块查询)
|
||||
.inIfPresent(ProductRequirementDO::getModuleId, reqVO.getModuleIds())
|
||||
// 模块ID精确匹配(当moduleIds为空时使用)
|
||||
.eqIfPresent(ProductRequirementDO::getModuleId, reqVO.getModuleId())
|
||||
// 父需求ID精确匹配(查询子需求时使用)
|
||||
.eqIfPresent(ProductRequirementDO::getParentId, reqVO.getParentId())
|
||||
@@ -90,9 +92,19 @@ public interface ProductRequirementMapper extends BaseMapperX<ProductRequirement
|
||||
* 带并发控制的状态更新(id + fromStatus 条件更新)
|
||||
*/
|
||||
default int updateStatusByIdAndStatus(Long id, String fromStatus, String toStatus, String lastStatusReason) {
|
||||
return updateStatusByIdAndStatusWithProject(id, fromStatus, toStatus, lastStatusReason, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带并发控制的状态更新(支持同时更新实现项目ID)
|
||||
*/
|
||||
default int updateStatusByIdAndStatusWithProject(Long id, String fromStatus, String toStatus, String lastStatusReason, Long implementProjectId) {
|
||||
ProductRequirementDO update = new ProductRequirementDO();
|
||||
update.setStatusCode(toStatus);
|
||||
update.setLastStatusReason(lastStatusReason);
|
||||
if (implementProjectId != null) {
|
||||
update.setImplementProjectId(implementProjectId);
|
||||
}
|
||||
return update(update, new LambdaQueryWrapperX<ProductRequirementDO>()
|
||||
.eq(ProductRequirementDO::getId, id)
|
||||
.eq(ProductRequirementDO::getStatusCode, fromStatus));
|
||||
|
||||
@@ -31,7 +31,7 @@ public interface ProductRequirementService {
|
||||
* @param id 需求编号
|
||||
* @return 需求详情
|
||||
*/
|
||||
ProductRequirementRespVO getRequirement(Long id);
|
||||
ProductRequirementRespVO getRequirement(Long id, Long productId);
|
||||
|
||||
/**
|
||||
* 获取需求分页列表
|
||||
@@ -42,13 +42,12 @@ public interface ProductRequirementService {
|
||||
PageResult<ProductRequirementRespVO> getRequirementPage(ProductRequirementPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获取需求树形列表(包含子需求)
|
||||
* 获取需求树形列表(分页)
|
||||
*
|
||||
* @param productId 产品编号
|
||||
* @param moduleId 模块编号(可为null)
|
||||
* @return 需求树形列表
|
||||
* @param pageReqVO 分页请求
|
||||
* @return 分页结果(只按父需求分页,子需求不计入分页)
|
||||
*/
|
||||
List<ProductRequirementRespVO> getRequirementTree(Long productId, Long moduleId);
|
||||
PageResult<ProductRequirementRespVO> getRequirementTree(ProductRequirementPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 变更需求状态
|
||||
@@ -106,14 +105,14 @@ public interface ProductRequirementService {
|
||||
* @param reqVO 模块保存请求
|
||||
* @return 模块编号
|
||||
*/
|
||||
Long createRequirementModule(ProductRequirementModuleSaveReqVO reqVO);
|
||||
Long createRequirementModule(ProductRequirementModuleReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 更新需求模块
|
||||
*
|
||||
* @param reqVO 模块保存请求
|
||||
*/
|
||||
void updateRequirementModule(ProductRequirementModuleSaveReqVO reqVO);
|
||||
void updateRequirementModule(ProductRequirementModuleReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 删除需求模块(级联删除模块下需求)
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -55,10 +56,16 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
private static final List<String> TERMINAL_STATUSES = List.of(STATUS_CLOSED, STATUS_REJECTED, STATUS_CANCELLED);
|
||||
// 子需求允许大需求关闭的状态集合
|
||||
private static final List<String> CHILD_ALLOW_CLOSE_STATUSES = List.of(STATUS_CLOSED, STATUS_CANCELLED, STATUS_REJECTED, STATUS_ACCEPTED);
|
||||
// 允许删除的状态集合(实施中之前的状态)
|
||||
private static final List<String> ALLOW_DELETE_STATUSES = List.of(STATUS_PENDING_CONFIRM, STATUS_PENDING_REVIEW, STATUS_PENDING_DISPATCH);
|
||||
|
||||
// 权限常量
|
||||
private static final String PRODUCT_CREATE_PERMISSION = "project:product:create";
|
||||
private static final String PRODUCT_QUERY_PERMISSION = "project:product:query";
|
||||
private static final String PRODUCT_UPDATE_PERMISSION = "project:product:update";
|
||||
private static final String PRODUCT_STATUS_PERMISSION = "project:product:status";
|
||||
private static final String PRODUCT_DELETE_PERMISSION = "project:product:delete";
|
||||
private static final String PRODUCT_SPLIT_PERMISSION = "project:product:split";
|
||||
|
||||
// 审计动作常量
|
||||
private static final String ACTION_CREATE = "create";
|
||||
@@ -86,7 +93,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#createReqVO.productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
permission = PRODUCT_CREATE_PERMISSION)
|
||||
public Long createRequirement(ProductRequirementSaveReqVO createReqVO) {
|
||||
// 当未选择模块时,自动归属到该产品的"全部需求"模块
|
||||
Long moduleId = resolveModuleId(createReqVO.getModuleId(), createReqVO.getProductId());
|
||||
@@ -152,7 +159,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public ProductRequirementRespVO getRequirement(Long id) {
|
||||
public ProductRequirementRespVO getRequirement(Long id, Long productId) {
|
||||
ProductRequirementDO requirement = validateRequirementExists(id);
|
||||
return buildRequirementRespVO(requirement);
|
||||
}
|
||||
@@ -161,10 +168,17 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#pageReqVO.productId",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public PageResult<ProductRequirementRespVO> getRequirementPage(ProductRequirementPageReqVO pageReqVO) {
|
||||
// 判断是否查询"全部需求"模块:通过查询模块的parentId是否为0L来判断
|
||||
if (pageReqVO.getModuleId() != null && isAllRequirementsModule(pageReqVO.getModuleId())) {
|
||||
// "全部需求"模块,忽略模块ID条件,查询该产品下所有需求
|
||||
pageReqVO.setModuleId(null);
|
||||
// 处理模块ID条件:支持递归查询子模块
|
||||
if (pageReqVO.getModuleId() != null) {
|
||||
if (isAllRequirementsModule(pageReqVO.getModuleId())) {
|
||||
// "全部需求"模块,忽略模块ID条件,查询该产品下所有需求
|
||||
pageReqVO.setModuleId(null);
|
||||
} else {
|
||||
// 非"全部需求"模块,获取该模块及其所有子模块的ID列表
|
||||
List<Long> moduleIds = getAllModuleIdsWithChildren(pageReqVO.getModuleId(), pageReqVO.getProductId());
|
||||
pageReqVO.setModuleIds(moduleIds);
|
||||
pageReqVO.setModuleId(null);
|
||||
}
|
||||
}
|
||||
PageResult<ProductRequirementDO> pageResult = requirementMapper.selectPage(pageReqVO);
|
||||
List<ProductRequirementRespVO> list = pageResult.getList().stream()
|
||||
@@ -174,28 +188,30 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
}
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#pageReqVO.productId",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public List<ProductRequirementRespVO> getRequirementTree(Long productId, Long moduleId) {
|
||||
public PageResult<ProductRequirementRespVO> getRequirementTree(ProductRequirementPageReqVO pageReqVO) {
|
||||
System.out.println("--------------");
|
||||
System.out.println(pageReqVO);
|
||||
// 查询当前产品下的所有顶级需求
|
||||
ProductRequirementPageReqVO pageReqVO = new ProductRequirementPageReqVO();
|
||||
pageReqVO.setProductId(productId);
|
||||
pageReqVO.setParentId(0L);
|
||||
pageReqVO.setPageSize(1000); // 树形查询取较大值
|
||||
Long moduleId = pageReqVO.getModuleId();
|
||||
Long productId = pageReqVO.getProductId();
|
||||
|
||||
// 判断是否查询"全部需求"模块:通过查询模块的parentId是否为0L来判断
|
||||
if (moduleId != null && !isAllRequirementsModule(moduleId)) {
|
||||
// 非"全部需求"模块,添加模块ID过滤条件
|
||||
pageReqVO.setModuleId(moduleId);
|
||||
// 处理模块过滤条件:仅当选中具体模块(非“全部需求”)时,才递归加载子模块ID进行过滤
|
||||
if (moduleId != null) {
|
||||
pageReqVO.setModuleIds(getAllModuleIdsWithChildren(moduleId, productId));
|
||||
// 清空moduleId,避免与moduleIds冲突(Mapper中优先使用moduleIds做IN查询)
|
||||
pageReqVO.setModuleId(null);
|
||||
}
|
||||
// 如果是"全部需求"模块,不设置moduleId条件,查询该产品下所有需求
|
||||
// 固定只查询父需求(parentId = 0L),子需求通过递归加载
|
||||
pageReqVO.setParentId(0L);
|
||||
PageResult<ProductRequirementDO> pageResult = requirementMapper.selectPage(pageReqVO);
|
||||
|
||||
List<ProductRequirementDO> parentList = requirementMapper.selectPage(pageReqVO).getList();
|
||||
|
||||
// 构建树形结构
|
||||
return parentList.stream()
|
||||
// 构建树形结构(子需求不计入分页)
|
||||
List<ProductRequirementRespVO> list = pageResult.getList().stream()
|
||||
.map(this::buildRequirementRespVOWithChildren)
|
||||
.collect(Collectors.toList());
|
||||
return new PageResult<>(list, pageResult.getTotal());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,13 +226,68 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
return module != null && module.getParentId() != null && module.getParentId() == 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取模块及其所有子模块的ID列表
|
||||
* @param moduleId 起始模块ID
|
||||
* @param productId 产品ID
|
||||
* @return 包含自身及所有子模块的ID列表
|
||||
*/
|
||||
@VisibleForTesting
|
||||
List<Long> getAllModuleIdsWithChildren(Long moduleId, Long productId) {
|
||||
List<Long> moduleIds = new ArrayList<>();
|
||||
moduleIds.add(moduleId);
|
||||
// 查询该产品下所有模块
|
||||
List<ProductRequirementModuleDO> allModules = moduleMapper.selectListByProductId(productId);
|
||||
// 递归查找子模块
|
||||
collectChildModuleIds(moduleId, allModules, moduleIds);
|
||||
return moduleIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归收集子模块ID
|
||||
*/
|
||||
private void collectChildModuleIds(Long parentId, List<ProductRequirementModuleDO> allModules, List<Long> result) {
|
||||
for (ProductRequirementModuleDO module : allModules) {
|
||||
if (Objects.equals(module.getParentId(), parentId)) {
|
||||
result.add(module.getId());
|
||||
collectChildModuleIds(module.getId(), allModules, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取需求及其所有子需求(包含子子需求)
|
||||
* @param requirementId 起始需求ID
|
||||
* @return 包含自身及所有后代需求的列表
|
||||
*/
|
||||
@VisibleForTesting
|
||||
List<ProductRequirementDO> getAllRequirementsWithChildren(Long requirementId) {
|
||||
List<ProductRequirementDO> allRequirements = new ArrayList<>();
|
||||
ProductRequirementDO requirement = validateRequirementExists(requirementId);
|
||||
allRequirements.add(requirement);
|
||||
collectChildRequirements(requirementId, allRequirements);
|
||||
return allRequirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归收集子需求
|
||||
*/
|
||||
private void collectChildRequirements(Long parentId, List<ProductRequirementDO> result) {
|
||||
List<ProductRequirementDO> children = requirementMapper.selectListByParentId(parentId);
|
||||
for (ProductRequirementDO child : children) {
|
||||
result.add(child);
|
||||
collectChildRequirements(child.getId(), result);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
permission = PRODUCT_STATUS_PERMISSION)
|
||||
public void changeRequirementStatus(ProductRequirementStatusActionReqVO reqVO) {
|
||||
ProductRequirementDO requirement = validateRequirementExists(reqVO.getId());
|
||||
String actionCode = reqVO.getActionCode().trim();
|
||||
Long implementProjectId = reqVO.getImplementProjectId();
|
||||
String fromStatus = requirement.getStatusCode();
|
||||
|
||||
// 校验状态流转是否合法
|
||||
@@ -224,16 +295,19 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
String reason = normalizeNullableText(reqVO.getReason());
|
||||
// 校验是否需要填写原因
|
||||
validateTransitionReason(transition, reason);
|
||||
|
||||
//下一状态
|
||||
String toStatus = transition.getToStatusCode();
|
||||
|
||||
// dispatch动作时更新实现项目
|
||||
if ("dispatch".equals(actionCode) && reqVO.getImplementProjectId() != null) {
|
||||
requirement.setImplementProjectId(reqVO.getImplementProjectId());
|
||||
// accept和close动作时,校验所有子需求(包括子子需求)是否处于允许状态
|
||||
if ("accept".equals(actionCode) || "close".equals(actionCode)) {
|
||||
validateAllChildrenAllowCloseOrAccept(reqVO.getId());
|
||||
}
|
||||
|
||||
// 带并发控制的状态更新
|
||||
int updateCount = requirementMapper.updateStatusByIdAndStatus(requirement.getId(), fromStatus, toStatus, reason);
|
||||
// close动作时,递归关闭所有已验收的子需求(包括子子需求)
|
||||
if ("close".equals(actionCode)) {
|
||||
closeAllAcceptedChildren(reqVO.getId(), reason);
|
||||
}
|
||||
// 带并发控制的状态更新(支持同时更新实现项目ID)
|
||||
int updateCount = requirementMapper.updateStatusByIdAndStatusWithProject(requirement.getId(), fromStatus, toStatus, reason, implementProjectId);
|
||||
if (updateCount != 1) {
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_CONCURRENT_MODIFIED);
|
||||
}
|
||||
@@ -246,14 +320,41 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
writeBizAuditLog(requirement, actionCode, fromStatus, toStatus, null, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验需求的所有子需求(包括子子需求)是否处于允许关闭或验收的状态
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void validateAllChildrenAllowCloseOrAccept(Long requirementId) {
|
||||
List<ProductRequirementDO> allChildren = getAllRequirementsWithChildren(requirementId);
|
||||
// 排除自身,只校验子需求
|
||||
for (ProductRequirementDO req : allChildren) {
|
||||
if (!Objects.equals(req.getId(), requirementId)) {
|
||||
if (!CHILD_ALLOW_CLOSE_STATUSES.contains(req.getStatusCode())) {
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_CHILD_NOT_ALLOW_CLOSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
permission = PRODUCT_DELETE_PERMISSION)
|
||||
public void deleteRequirement(Long id, Long productId) {
|
||||
ProductRequirementDO requirement = validateRequirementExists(id);
|
||||
String fromStatus = requirement.getStatusCode();
|
||||
|
||||
// 校验是否存在子需求
|
||||
List<ProductRequirementDO> children = requirementMapper.selectListByParentId(id);
|
||||
if (!children.isEmpty()) {
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_HAS_CHILDREN);
|
||||
}
|
||||
|
||||
// 校验状态是否允许删除(只有待确认、待评审、待分流状态才能删除)
|
||||
if (!ALLOW_DELETE_STATUSES.contains(fromStatus)) {
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_NOT_ALLOW_DELETE);
|
||||
}
|
||||
|
||||
// 带并发控制的删除(以当前状态作为条件)
|
||||
int deleteCount = requirementMapper.deleteByIdAndStatus(id, fromStatus);
|
||||
if (deleteCount != 1) {
|
||||
@@ -266,7 +367,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
permission = PRODUCT_SPLIT_PERMISSION)
|
||||
public Long splitRequirement(ProductRequirementSplitReqVO reqVO) {
|
||||
// 校验父需求是否存在
|
||||
ProductRequirementDO parentRequirement = validateRequirementExists(reqVO.getParentId());
|
||||
@@ -276,15 +377,19 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
// 创建子需求
|
||||
ProductRequirementDO childRequirement = new ProductRequirementDO();
|
||||
childRequirement.setParentId(reqVO.getParentId());
|
||||
childRequirement.setProductId(parentRequirement.getProductId()); // 子需求继承父需求的产品
|
||||
childRequirement.setModuleId(parentRequirement.getModuleId()); // 子需求继承父需求的模块
|
||||
childRequirement.setReviewRequired(0); // 子需求默认不需要评审
|
||||
childRequirement.setReviewRequired(reqVO.getReviewRequired()); // 子需求默认不需要评审
|
||||
childRequirement.setTitle(reqVO.getTitle().trim());
|
||||
childRequirement.setDescription(normalizeNullableText(reqVO.getDescription()));
|
||||
childRequirement.setCategory(reqVO.getCategory());
|
||||
childRequirement.setSourceType(parentRequirement.getSourceType()); // 继承父需求来源类型
|
||||
childRequirement.setPriority(reqVO.getPriority());
|
||||
// 子需求初始状态为待分流
|
||||
childRequirement.setStatusCode(STATUS_PENDING_DISPATCH);
|
||||
// 根据是否需要评审确定初始状态
|
||||
String initialStatus = Objects.equals(reqVO.getReviewRequired(), 1)
|
||||
? STATUS_PENDING_REVIEW : STATUS_PENDING_DISPATCH;
|
||||
childRequirement.setStatusCode(initialStatus);
|
||||
childRequirement.setProposerId(parentRequirement.getProposerId()); // 继承父需求提出人
|
||||
childRequirement.setCurrentHandlerUserId(reqVO.getCurrentHandlerUserId());
|
||||
childRequirement.setImplementProjectId(reqVO.getImplementProjectId());
|
||||
@@ -305,14 +410,14 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
}
|
||||
|
||||
// 写入子需求的业务审计日志
|
||||
writeBizAuditLog(childRequirement, ACTION_CREATE, null, STATUS_PENDING_DISPATCH, null, null);
|
||||
writeBizAuditLog(childRequirement, ACTION_CREATE, null, initialStatus, null, null);
|
||||
return childRequirement.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
permission = PRODUCT_STATUS_PERMISSION)
|
||||
public void closeRequirement(ProductRequirementCloseReqVO reqVO) {
|
||||
ProductRequirementDO requirement = validateRequirementExists(reqVO.getId());
|
||||
String fromStatus = requirement.getStatusCode();
|
||||
@@ -323,26 +428,11 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_NOT_ALLOW_CLOSE);
|
||||
}
|
||||
|
||||
// 如果是父需求,校验所有子需求是否允许关闭
|
||||
List<ProductRequirementDO> children = requirementMapper.selectListByParentId(requirement.getId());
|
||||
if (!children.isEmpty()) {
|
||||
for (ProductRequirementDO child : children) {
|
||||
if (!CHILD_ALLOW_CLOSE_STATUSES.contains(child.getStatusCode())) {
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_CHILD_NOT_ALLOW_CLOSE);
|
||||
}
|
||||
}
|
||||
// 将所有已验收的子需求关闭
|
||||
for (ProductRequirementDO child : children) {
|
||||
if (STATUS_ACCEPTED.equals(child.getStatusCode())) {
|
||||
int updateCount = requirementMapper.updateStatusByIdAndStatus(
|
||||
child.getId(), STATUS_ACCEPTED, STATUS_CLOSED, reason);
|
||||
if (updateCount == 1) {
|
||||
writeRequirementStatusLog(child, ACTION_CLOSE, STATUS_ACCEPTED, STATUS_CLOSED, reason);
|
||||
writeBizAuditLog(child, ACTION_CLOSE, STATUS_ACCEPTED, STATUS_CLOSED, null, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 校验所有子需求(包括子子需求)是否允许关闭
|
||||
validateAllChildrenAllowCloseOrAccept(reqVO.getId());
|
||||
|
||||
// 递归关闭所有已验收的子需求(包括子子需求)
|
||||
closeAllAcceptedChildren(reqVO.getId(), reason);
|
||||
|
||||
// 关闭当前需求
|
||||
int updateCount = requirementMapper.updateStatusByIdAndStatus(
|
||||
@@ -357,6 +447,26 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
writeBizAuditLog(requirement, ACTION_CLOSE, fromStatus, STATUS_CLOSED, null, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归关闭所有已验收的子需求(包括子子需求)
|
||||
*/
|
||||
private void closeAllAcceptedChildren(Long parentId, String reason) {
|
||||
List<ProductRequirementDO> children = requirementMapper.selectListByParentId(parentId);
|
||||
for (ProductRequirementDO child : children) {
|
||||
// 递归处理子需求的子需求
|
||||
closeAllAcceptedChildren(child.getId(), reason);
|
||||
// 如果子需求已验收,则关闭
|
||||
if (STATUS_ACCEPTED.equals(child.getStatusCode())) {
|
||||
int updateCount = requirementMapper.updateStatusByIdAndStatus(
|
||||
child.getId(), STATUS_ACCEPTED, STATUS_CLOSED, reason);
|
||||
if (updateCount == 1) {
|
||||
writeRequirementStatusLog(child, ACTION_CLOSE, STATUS_ACCEPTED, STATUS_CLOSED, reason);
|
||||
writeBizAuditLog(child, ACTION_CLOSE, STATUS_ACCEPTED, STATUS_CLOSED, null, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
@@ -410,8 +520,8 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
public Long createRequirementModule(ProductRequirementModuleSaveReqVO reqVO) {
|
||||
permission = PRODUCT_CREATE_PERMISSION)
|
||||
public Long createRequirementModule(ProductRequirementModuleReqVO reqVO) {
|
||||
// 校验模块名称在同一产品下是否唯一
|
||||
validateModuleNameUnique(reqVO.getProductId(), null, reqVO.getModuleName());
|
||||
|
||||
@@ -430,7 +540,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
public void updateRequirementModule(ProductRequirementModuleSaveReqVO reqVO) {
|
||||
public void updateRequirementModule(ProductRequirementModuleReqVO reqVO) {
|
||||
if (reqVO.getId() == null) {
|
||||
throw invalidParamException("模块编号不能为空");
|
||||
}
|
||||
@@ -448,22 +558,20 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
permission = PRODUCT_DELETE_PERMISSION)
|
||||
public void deleteRequirementModule(Long moduleId, Long productId) {
|
||||
ProductRequirementModuleDO module = validateModuleExists(moduleId);
|
||||
validateModuleExists(moduleId);
|
||||
|
||||
// 查询模块下的所有需求
|
||||
List<ProductRequirementDO> requirements = requirementMapper.selectListByModuleId(moduleId);
|
||||
// 校验是否存在非终态需求
|
||||
for (ProductRequirementDO requirement : requirements) {
|
||||
if (!TERMINAL_STATUSES.contains(requirement.getStatusCode())) {
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_HAS_NON_TERMINAL_REQUIREMENTS);
|
||||
}
|
||||
// 校验是否存在子模块
|
||||
List<ProductRequirementModuleDO> childModules = moduleMapper.selectListByParentId(moduleId);
|
||||
if (!childModules.isEmpty()) {
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_HAS_CHILDREN);
|
||||
}
|
||||
|
||||
// 级联软删除模块下的所有需求
|
||||
for (ProductRequirementDO requirement : requirements) {
|
||||
requirementMapper.deleteById(requirement.getId());
|
||||
// 校验模块下是否存在需求
|
||||
List<ProductRequirementDO> requirements = requirementMapper.selectListByModuleId(moduleId);
|
||||
if (!requirements.isEmpty()) {
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_HAS_REQUIREMENTS);
|
||||
}
|
||||
|
||||
// 删除模块
|
||||
@@ -508,7 +616,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
List<ProductRequirementDO> children = requirementMapper.selectListByParentId(requirement.getId());
|
||||
if (!children.isEmpty()) {
|
||||
respVO.setChildren(children.stream()
|
||||
.map(this::buildRequirementRespVO)
|
||||
.map(this::buildRequirementRespVOWithChildren)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return respVO;
|
||||
|
||||
@@ -6,14 +6,6 @@ import com.njcn.rdms.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.rdms.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.rdms.module.project.constant.ObjectActivityConstants;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextNavRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextProductRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextRoleRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductDeleteReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductSaveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductStatusActionReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.*;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingBaseInfoUpdateReqVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
|
||||
|
||||
@@ -486,7 +486,7 @@ class ProductRequirementServiceImplTest extends BaseMockitoUnitTest {
|
||||
existModule.setProductId(productId);
|
||||
existModule.setModuleName("核心功能");
|
||||
|
||||
ProductRequirementModuleSaveReqVO reqVO = new ProductRequirementModuleSaveReqVO();
|
||||
ProductRequirementModuleReqVO reqVO = new ProductRequirementModuleReqVO();
|
||||
reqVO.setProductId(productId);
|
||||
reqVO.setModuleName("核心功能");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user