From 7913c210cd980bee56205c1eeb5ffbd4356b0f82 Mon Sep 17 00:00:00 2001 From: dk <1260500659@qq.com> Date: Wed, 6 May 2026 17:49:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E4=BA=A7=E5=93=81=E9=9C=80=E6=B1=82):=20?= =?UTF-8?q?=E4=BA=A7=E5=93=81=E9=9C=80=E6=B1=82=E7=9B=B8=E5=85=B3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/enums/ErrorCodeConstants.java | 6 +- .../product/ProductRequirementController.java | 45 +--- .../ProductRequirementDeleteReqVO.java | 19 ++ .../ProductRequirementModuleDeleteReqVO.java | 19 ++ ...ava => ProductRequirementModuleReqVO.java} | 2 +- .../ProductRequirementPageReqVO.java | 6 + .../ProductRequirementSplitReqVO.java | 14 +- .../ProductRequirementStatusActionReqVO.java | 5 +- .../product/ProductRequirementMapper.java | 14 +- .../product/ProductRequirementService.java | 15 +- .../ProductRequirementServiceImpl.java | 250 +++++++++++++----- .../service/product/ProductServiceImpl.java | 8 - .../ProductRequirementServiceImplTest.java | 2 +- 13 files changed, 279 insertions(+), 126 deletions(-) create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementDeleteReqVO.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementModuleDeleteReqVO.java rename rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/{ProductRequirementModuleSaveReqVO.java => ProductRequirementModuleReqVO.java} (96%) diff --git a/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java b/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java index a2a98b4..0ff5b20 100644 --- a/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java +++ b/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java @@ -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, "模块下存在需求,请先删除需求"); } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/ProductRequirementController.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/ProductRequirementController.java index ce90146..6bbfe55 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/ProductRequirementController.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/ProductRequirementController.java @@ -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 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> getRequirementTree(@RequestParam("productId") Long productId, - @RequestParam(value = "moduleId", required = false) Long moduleId) { - return success(requirementService.getRequirementTree(productId, moduleId)); + @Operation(summary = "获取需求树形列表(分页)") + public CommonResult> 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 deleteRequirement(@RequestParam("id") Long id, - @RequestParam("productId") Long productId) { - requirementService.deleteRequirement(id, productId); + public CommonResult deleteRequirement(@Valid @RequestBody ProductRequirementDeleteReqVO reqVO) { + requirementService.deleteRequirement(reqVO.getId(), reqVO.getProductId()); return success(true); } @PostMapping("/split") @Operation(summary = "拆分产品需求") public CommonResult 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 createRequirementModule(@Valid @RequestBody ProductRequirementModuleSaveReqVO reqVO) { + public CommonResult createRequirementModule(@Valid @RequestBody ProductRequirementModuleReqVO reqVO) { return success(requirementService.createRequirementModule(reqVO)); } @PutMapping("/module/update") @Operation(summary = "更新需求模块") - public CommonResult updateRequirementModule(@Valid @RequestBody ProductRequirementModuleSaveReqVO reqVO) { + public CommonResult 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 deleteRequirementModule(@RequestParam("moduleId") Long moduleId, - @RequestParam("productId") Long productId) { - requirementService.deleteRequirementModule(moduleId, productId); + public CommonResult deleteRequirementModule(@Valid @RequestBody ProductRequirementModuleDeleteReqVO reqVO) { + requirementService.deleteRequirementModule(reqVO.getId(), reqVO.getProductId()); return success(true); } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementDeleteReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementDeleteReqVO.java new file mode 100644 index 0000000..374fc90 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementDeleteReqVO.java @@ -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; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementModuleDeleteReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementModuleDeleteReqVO.java new file mode 100644 index 0000000..d3399f9 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementModuleDeleteReqVO.java @@ -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; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementModuleSaveReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementModuleReqVO.java similarity index 96% rename from rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementModuleSaveReqVO.java rename to rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementModuleReqVO.java index b4c6858..a82cda7 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementModuleSaveReqVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementModuleReqVO.java @@ -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; diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementPageReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementPageReqVO.java index a4d4b35..fe85595 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementPageReqVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementPageReqVO.java @@ -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 moduleIds; + @Schema(description = "父需求ID(查询子需求时使用)", example = "1024") private Long parentId; diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementSplitReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementSplitReqVO.java index bc39b68..bc975ae 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementSplitReqVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementSplitReqVO.java @@ -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; - } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementStatusActionReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementStatusActionReqVO.java index 09cc935..6b27fbb 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementStatusActionReqVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/product/vo/requirement/ProductRequirementStatusActionReqVO.java @@ -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; - } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/product/ProductRequirementMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/product/ProductRequirementMapper.java index 6fe9f75..5dab8f0 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/product/ProductRequirementMapper.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/product/ProductRequirementMapper.java @@ -35,7 +35,9 @@ public interface ProductRequirementMapper extends BaseMapperX() .eq(ProductRequirementDO::getId, id) .eq(ProductRequirementDO::getStatusCode, fromStatus)); diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/product/ProductRequirementService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/product/ProductRequirementService.java index 9702ded..397c97c 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/product/ProductRequirementService.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/product/ProductRequirementService.java @@ -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 getRequirementPage(ProductRequirementPageReqVO pageReqVO); /** - * 获取需求树形列表(包含子需求) + * 获取需求树形列表(分页) * - * @param productId 产品编号 - * @param moduleId 模块编号(可为null) - * @return 需求树形列表 + * @param pageReqVO 分页请求 + * @return 分页结果(只按父需求分页,子需求不计入分页) */ - List getRequirementTree(Long productId, Long moduleId); + PageResult 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); /** * 删除需求模块(级联删除模块下需求) diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/product/ProductRequirementServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/product/ProductRequirementServiceImpl.java index fdfcef7..cd12409 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/product/ProductRequirementServiceImpl.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/product/ProductRequirementServiceImpl.java @@ -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 TERMINAL_STATUSES = List.of(STATUS_CLOSED, STATUS_REJECTED, STATUS_CANCELLED); // 子需求允许大需求关闭的状态集合 private static final List CHILD_ALLOW_CLOSE_STATUSES = List.of(STATUS_CLOSED, STATUS_CANCELLED, STATUS_REJECTED, STATUS_ACCEPTED); + // 允许删除的状态集合(实施中之前的状态) + private static final List 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 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 moduleIds = getAllModuleIdsWithChildren(pageReqVO.getModuleId(), pageReqVO.getProductId()); + pageReqVO.setModuleIds(moduleIds); + pageReqVO.setModuleId(null); + } } PageResult pageResult = requirementMapper.selectPage(pageReqVO); List 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 getRequirementTree(Long productId, Long moduleId) { + public PageResult 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 pageResult = requirementMapper.selectPage(pageReqVO); - List parentList = requirementMapper.selectPage(pageReqVO).getList(); - - // 构建树形结构 - return parentList.stream() + // 构建树形结构(子需求不计入分页) + List 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 getAllModuleIdsWithChildren(Long moduleId, Long productId) { + List moduleIds = new ArrayList<>(); + moduleIds.add(moduleId); + // 查询该产品下所有模块 + List allModules = moduleMapper.selectListByProductId(productId); + // 递归查找子模块 + collectChildModuleIds(moduleId, allModules, moduleIds); + return moduleIds; + } + + /** + * 递归收集子模块ID + */ + private void collectChildModuleIds(Long parentId, List allModules, List 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 getAllRequirementsWithChildren(Long requirementId) { + List allRequirements = new ArrayList<>(); + ProductRequirementDO requirement = validateRequirementExists(requirementId); + allRequirements.add(requirement); + collectChildRequirements(requirementId, allRequirements); + return allRequirements; + } + + /** + * 递归收集子需求 + */ + private void collectChildRequirements(Long parentId, List result) { + List 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 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 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 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 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 requirements = requirementMapper.selectListByModuleId(moduleId); - // 校验是否存在非终态需求 - for (ProductRequirementDO requirement : requirements) { - if (!TERMINAL_STATUSES.contains(requirement.getStatusCode())) { - throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_HAS_NON_TERMINAL_REQUIREMENTS); - } + // 校验是否存在子模块 + List childModules = moduleMapper.selectListByParentId(moduleId); + if (!childModules.isEmpty()) { + throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_HAS_CHILDREN); } - // 级联软删除模块下的所有需求 - for (ProductRequirementDO requirement : requirements) { - requirementMapper.deleteById(requirement.getId()); + // 校验模块下是否存在需求 + List requirements = requirementMapper.selectListByModuleId(moduleId); + if (!requirements.isEmpty()) { + throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_HAS_REQUIREMENTS); } // 删除模块 @@ -508,7 +616,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService List children = requirementMapper.selectListByParentId(requirement.getId()); if (!children.isEmpty()) { respVO.setChildren(children.stream() - .map(this::buildRequirementRespVO) + .map(this::buildRequirementRespVOWithChildren) .collect(Collectors.toList())); } return respVO; diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/product/ProductServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/product/ProductServiceImpl.java index 8622957..3af5efd 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/product/ProductServiceImpl.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/product/ProductServiceImpl.java @@ -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; diff --git a/rdms-project/rdms-project-boot/src/test/java/com/njcn/rdms/module/project/service/product/ProductRequirementServiceImplTest.java b/rdms-project/rdms-project-boot/src/test/java/com/njcn/rdms/module/project/service/product/ProductRequirementServiceImplTest.java index 845a0b9..45d2889 100644 --- a/rdms-project/rdms-project-boot/src/test/java/com/njcn/rdms/module/project/service/product/ProductRequirementServiceImplTest.java +++ b/rdms-project/rdms-project-boot/src/test/java/com/njcn/rdms/module/project/service/product/ProductRequirementServiceImplTest.java @@ -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("核心功能");