feat(项目需求): 开发项目需求。
This commit is contained in:
@@ -81,8 +81,6 @@ public class ProductRequirementController {
|
||||
@PostMapping("/split")
|
||||
@Operation(summary = "拆分产品需求")
|
||||
public CommonResult<Long> splitRequirement(@Valid @RequestBody ProductRequirementSplitReqVO reqVO) {
|
||||
System.out.println("-----------------------");
|
||||
System.out.println(reqVO);
|
||||
return success(requirementService.splitRequirement(reqVO));
|
||||
}
|
||||
|
||||
@@ -103,6 +101,16 @@ public class ProductRequirementController {
|
||||
return success(requirementService.getAllowedTransitions(requirementId, productId));
|
||||
}
|
||||
|
||||
@GetMapping("/has-dispatched")
|
||||
@Operation(summary = "判断产品需求是否已分流生成项目需求")
|
||||
@Parameter(name = "requirementId", description = "需求编号", required = true, example = "1024")
|
||||
@Parameter(name = "productId", description = "产品编号", required = true, example = "1024")
|
||||
public CommonResult<Boolean> hasDispatchedProjectRequirement(
|
||||
@RequestParam("requirementId") Long requirementId,
|
||||
@RequestParam("productId") Long productId) {
|
||||
return success(requirementService.hasDispatchedProjectRequirement(requirementId, productId));
|
||||
}
|
||||
|
||||
@GetMapping("/lifecycle")
|
||||
@Operation(summary = "获取需求生命周期信息")
|
||||
@Parameter(name = "requirementId", description = "需求编号", required = true, example = "1024")
|
||||
@@ -113,8 +121,15 @@ public class ProductRequirementController {
|
||||
return success(requirementService.getRequirementLifecycle(requirementId, productId));
|
||||
}
|
||||
|
||||
// ========== 模块管理 ==========
|
||||
@GetMapping("/dispatched-project-link")
|
||||
@Operation(summary = "获取产品需求分流后对应的项目需求跳转链接")
|
||||
@Parameter(name = "productRequirementId", description = "产品需求编号", required = true, example = "1024")
|
||||
public CommonResult<ProductRequirementDispatchedProjectLinkRespVO> getDispatchedProjectLink(
|
||||
@RequestParam("productRequirementId") Long productRequirementId) {
|
||||
return success(requirementService.getDispatchedProjectLink(productRequirementId));
|
||||
}
|
||||
|
||||
// ========== 模块管理 ==========
|
||||
@PostMapping("/module/create")
|
||||
@Operation(summary = "创建需求模块")
|
||||
public CommonResult<Long> createRequirementModule(@Valid @RequestBody ProductRequirementModuleReqVO reqVO) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 管理后台 - 产品需求分流后项目需求跳转链接 Response VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 产品需求分流后项目需求跳转链接 Response VO")
|
||||
@Data
|
||||
public class ProductRequirementDispatchedProjectLinkRespVO {
|
||||
|
||||
@Schema(description = "项目需求ID", example = "10086")
|
||||
private Long projectRequirementId;
|
||||
|
||||
@Schema(description = "实现项目ID", example = "8888")
|
||||
private Long projectId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementCloseReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementDeleteReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementLifecycleRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementModuleDeleteReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementModuleReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementModuleRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementSaveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementSplitReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementStatusActionReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementStatusDictRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementStatusTransitionRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementUpdateReqVO;
|
||||
import com.njcn.rdms.module.project.service.project.ProjectRequirementService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* 管理后台 - 项目需求控制器
|
||||
*/
|
||||
@Tag(name = "管理后台 - 项目需求")
|
||||
@RestController
|
||||
@RequestMapping("/project/project/requirement")
|
||||
@Validated
|
||||
public class ProjectRequirementController {
|
||||
|
||||
@Resource
|
||||
private ProjectRequirementService requirementService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建项目需求")
|
||||
public CommonResult<Long> createRequirement(@Valid @RequestBody ProjectRequirementSaveReqVO createReqVO) {
|
||||
return success(requirementService.createRequirement(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新项目需求")
|
||||
public CommonResult<Boolean> updateRequirement(@Valid @RequestBody ProjectRequirementUpdateReqVO updateReqVO) {
|
||||
requirementService.updateRequirement(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获取需求详情")
|
||||
@Parameter(name = "id", description = "需求编号", required = true, example = "1024")
|
||||
@Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
|
||||
public CommonResult<ProjectRequirementRespVO> getRequirement(@RequestParam("id") Long id,
|
||||
@RequestParam("projectId") Long projectId) {
|
||||
return success(requirementService.getRequirement(id, projectId));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获取需求分页列表")
|
||||
public CommonResult<PageResult<ProjectRequirementRespVO>> getRequirementPage(@Valid ProjectRequirementPageReqVO pageReqVO) {
|
||||
return success(requirementService.getRequirementPage(pageReqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/tree")
|
||||
@Operation(summary = "获取需求树形列表")
|
||||
public CommonResult<PageResult<ProjectRequirementRespVO>> getRequirementTree(@Valid ProjectRequirementPageReqVO pageReqVO) {
|
||||
return success(requirementService.getRequirementTree(pageReqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/change-status")
|
||||
@Operation(summary = "变更需求状态")
|
||||
public CommonResult<Boolean> changeRequirementStatus(@Valid @RequestBody ProjectRequirementStatusActionReqVO reqVO) {
|
||||
requirementService.changeRequirementStatus(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
@Operation(summary = "删除项目需求")
|
||||
public CommonResult<Boolean> deleteRequirement(@Valid @RequestBody ProjectRequirementDeleteReqVO reqVO) {
|
||||
requirementService.deleteRequirement(reqVO.getId(), reqVO.getProjectId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/split")
|
||||
@Operation(summary = "拆分项目需求")
|
||||
public CommonResult<Long> splitRequirement(@Valid @RequestBody ProjectRequirementSplitReqVO reqVO) {
|
||||
return success(requirementService.splitRequirement(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/close")
|
||||
@Operation(summary = "关闭项目需求")
|
||||
public CommonResult<Boolean> closeRequirement(@Valid @RequestBody ProjectRequirementCloseReqVO reqVO) {
|
||||
requirementService.closeRequirement(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/allowed-transitions")
|
||||
@Operation(summary = "获取需求可执行的状态动作列表")
|
||||
@Parameter(name = "requirementId", description = "需求编号", required = true, example = "1024")
|
||||
@Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
|
||||
public CommonResult<List<ProjectRequirementStatusTransitionRespVO>> getAllowedTransitions(
|
||||
@RequestParam("requirementId") Long requirementId,
|
||||
@RequestParam("projectId") Long projectId) {
|
||||
return success(requirementService.getAllowedTransitions(requirementId, projectId));
|
||||
}
|
||||
|
||||
@GetMapping("/lifecycle")
|
||||
@Operation(summary = "获取需求生命周期信息")
|
||||
@Parameter(name = "requirementId", description = "需求编号", required = true, example = "1024")
|
||||
@Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
|
||||
public CommonResult<ProjectRequirementLifecycleRespVO> getRequirementLifecycle(
|
||||
@RequestParam("requirementId") Long requirementId,
|
||||
@RequestParam("projectId") Long projectId) {
|
||||
return success(requirementService.getRequirementLifecycle(requirementId, projectId));
|
||||
}
|
||||
|
||||
@PostMapping("/module/create")
|
||||
@Operation(summary = "创建需求模块")
|
||||
public CommonResult<Long> createRequirementModule(@Valid @RequestBody ProjectRequirementModuleReqVO reqVO) {
|
||||
return success(requirementService.createRequirementModule(reqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/module/update")
|
||||
@Operation(summary = "更新需求模块")
|
||||
public CommonResult<Boolean> updateRequirementModule(@Valid @RequestBody ProjectRequirementModuleReqVO reqVO) {
|
||||
requirementService.updateRequirementModule(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/module/delete")
|
||||
@Operation(summary = "删除需求模块")
|
||||
public CommonResult<Boolean> deleteRequirementModule(@Valid @RequestBody ProjectRequirementModuleDeleteReqVO reqVO) {
|
||||
requirementService.deleteRequirementModule(reqVO.getId(), reqVO.getProjectId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/module/tree")
|
||||
@Operation(summary = "获取需求模块树")
|
||||
@Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
|
||||
public CommonResult<List<ProjectRequirementModuleRespVO>> getRequirementModuleTree(@RequestParam("projectId") Long projectId) {
|
||||
return success(requirementService.getRequirementModuleTree(projectId));
|
||||
}
|
||||
|
||||
@GetMapping("/status/dict")
|
||||
@Operation(summary = "获取需求所有状态字典")
|
||||
public CommonResult<List<ProjectRequirementStatusDictRespVO>> getRequirementStatusDict() {
|
||||
return success(requirementService.getRequirementStatusDict());
|
||||
}
|
||||
|
||||
@GetMapping("/status/dict/terminal")
|
||||
@Operation(summary = "获取需求终态状态字典")
|
||||
public CommonResult<List<ProjectRequirementStatusDictRespVO>> getRequirementTerminalStatusDict() {
|
||||
return success(requirementService.getRequirementTerminalStatusDict());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 管理后台 - 项目需求关闭 Request VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 项目需求关闭 Request VO")
|
||||
@Data
|
||||
public class ProjectRequirementCloseReqVO {
|
||||
|
||||
@Schema(description = "需求 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "需求 ID 不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "项目 ID 不能为空")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "关闭原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "需求已完成验收")
|
||||
@NotBlank(message = "关闭原因不能为空")
|
||||
@Size(max = 255, message = "关闭原因长度不能超过 255 个字符")
|
||||
private String reason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.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 ProjectRequirementDeleteReqVO {
|
||||
|
||||
@Schema(description = "需求 ID", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "项目 ID 不能为空")
|
||||
private Long projectId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 管理后台 - 项目需求生命周期 Response VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 项目需求生命周期 Response VO")
|
||||
@Data
|
||||
public class ProjectRequirementLifecycleRespVO {
|
||||
|
||||
@Schema(description = "当前状态编码", example = "implementing")
|
||||
private String statusCode;
|
||||
|
||||
@Schema(description = "当前状态名称", example = "实施中")
|
||||
private String statusName;
|
||||
|
||||
@Schema(description = "最近一次状态动作原因", example = "评审通过")
|
||||
private String lastStatusReason;
|
||||
|
||||
@Schema(description = "是否终态", example = "false")
|
||||
private Boolean terminal;
|
||||
|
||||
@Schema(description = "是否允许编辑", example = "true")
|
||||
private Boolean allowEdit;
|
||||
|
||||
@Schema(description = "当前状态可执行动作列表")
|
||||
private List<ProjectRequirementStatusTransitionRespVO> availableActions;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.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 ProjectRequirementModuleDeleteReqVO {
|
||||
|
||||
@Schema(description = "模块 ID", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "项目 ID 不能为空")
|
||||
private Long projectId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 管理后台 - 项目需求模块保存 Request VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 项目需求模块保存 Request VO")
|
||||
@Data
|
||||
public class ProjectRequirementModuleReqVO {
|
||||
|
||||
@Schema(description = "模块 ID", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "项目 ID 不能为空")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "父模块 ID,0 表示顶级", example = "0")
|
||||
private Long parentId;
|
||||
|
||||
@Schema(description = "模块名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "核心功能")
|
||||
@NotBlank(message = "模块名称不能为空")
|
||||
@Size(max = 100, message = "模块名称长度不能超过 100 个字符")
|
||||
private String moduleName;
|
||||
|
||||
@Schema(description = "模块说明", example = "项目核心功能模块")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "图标", example = "icon-function")
|
||||
private String icon;
|
||||
|
||||
@Schema(description = "排序值", example = "0")
|
||||
private Integer sort;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 管理后台 - 项目需求模块 Response VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 项目需求模块 Response VO")
|
||||
@Data
|
||||
public class ProjectRequirementModuleRespVO {
|
||||
|
||||
@Schema(description = "模块 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "父模块 ID,0 表示顶级", example = "0")
|
||||
private Long parentId;
|
||||
|
||||
@Schema(description = "所属项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "模块名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "核心功能")
|
||||
private String moduleName;
|
||||
|
||||
@Schema(description = "模块说明", example = "项目核心功能模块")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "图标", example = "icon-function")
|
||||
private String icon;
|
||||
|
||||
@Schema(description = "排序值", example = "0")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "子模块列表")
|
||||
private List<ProjectRequirementModuleRespVO> children;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
|
||||
|
||||
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
|
||||
*/
|
||||
@Schema(description = "管理后台 - 项目需求分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectRequirementPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "所属项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long projectId;
|
||||
|
||||
@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;
|
||||
|
||||
@Schema(description = "标题关键字", example = "模块")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "需求分类字典值", example = "function")
|
||||
private String category;
|
||||
|
||||
@Schema(description = "优先级", example = "1")
|
||||
private Integer priority;
|
||||
|
||||
@Schema(description = "状态编码", example = "implementing")
|
||||
private String statusCode;
|
||||
|
||||
@Schema(description = "当前处理人用户 ID", example = "1024")
|
||||
private Long currentHandlerUserId;
|
||||
|
||||
@Schema(description = "来源类型", example = "manual")
|
||||
private String sourceType;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 管理后台 - 项目需求 Response VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 项目需求 Response VO")
|
||||
@Data
|
||||
public class ProjectRequirementRespVO {
|
||||
|
||||
@Schema(description = "需求 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "父需求 ID,0 表示顶级需求", example = "0")
|
||||
private Long parentId;
|
||||
|
||||
@Schema(description = "所属项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "所属模块 ID", example = "1024")
|
||||
private Long moduleId;
|
||||
|
||||
@Schema(description = "是否需要评审,0 不需要,1 需要", example = "0")
|
||||
private Integer reviewRequired;
|
||||
|
||||
@Schema(description = "需求标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "支持需求模块化管理")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "需求描述,支持富文本", example = "<p>详细描述需求内容</p>")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "需求分类字典值", example = "function")
|
||||
private String category;
|
||||
|
||||
@Schema(description = "需求分类名称", example = "功能需求")
|
||||
private String categoryName;
|
||||
|
||||
@Schema(description = "需求来源类型", example = "manual")
|
||||
private String sourceType;
|
||||
|
||||
@Schema(description = "来源业务 ID", example = "1024")
|
||||
private Long sourceBizId;
|
||||
|
||||
@Schema(description = "优先级", example = "1")
|
||||
private Integer priority;
|
||||
|
||||
@Schema(description = "优先级名称", example = "中")
|
||||
private String priorityName;
|
||||
|
||||
@Schema(description = "当前状态编码", example = "implementing")
|
||||
private String statusCode;
|
||||
|
||||
@Schema(description = "当前状态名称", example = "实施中")
|
||||
private String statusName;
|
||||
|
||||
@Schema(description = "最近一次状态动作原因", example = "评审通过")
|
||||
private String lastStatusReason;
|
||||
|
||||
@Schema(description = "提出人用户 ID", example = "1024")
|
||||
private Long proposerId;
|
||||
|
||||
@Schema(description = "提出人昵称", example = "张三")
|
||||
private String proposerNickname;
|
||||
|
||||
@Schema(description = "所需工时", example = "8")
|
||||
private Double workHours;
|
||||
|
||||
@Schema(description = "当前处理人用户 ID", example = "1024")
|
||||
private Long currentHandlerUserId;
|
||||
|
||||
@Schema(description = "当前处理人昵称", example = "李四")
|
||||
private String currentHandlerUserNickname;
|
||||
|
||||
@Schema(description = "排序值", example = "0")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "子需求列表,树形结构")
|
||||
private List<ProjectRequirementRespVO> children;
|
||||
|
||||
@Schema(description = "是否为终态", example = "false")
|
||||
private Boolean terminal;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 管理后台 - 项目需求保存 Request VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 项目需求保存 Request VO")
|
||||
@Data
|
||||
public class ProjectRequirementSaveReqVO {
|
||||
|
||||
@Schema(description = "需求 ID", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "项目 ID 不能为空")
|
||||
private Long projectId;
|
||||
|
||||
@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 个字符")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "需求描述,支持富文本", example = "<p>详细描述需求内容</p>")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "需求分类字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "function")
|
||||
@NotBlank(message = "需求分类不能为空")
|
||||
@Size(max = 64, message = "需求分类长度不能超过 64 个字符")
|
||||
private String category;
|
||||
|
||||
@Schema(description = "优先级,0 低、1 中、2 高、3 紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "优先级不能为空")
|
||||
private Integer priority;
|
||||
|
||||
@Schema(description = "提出人用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "提出人不能为空")
|
||||
private Long proposerId;
|
||||
|
||||
@Schema(description = "提出人昵称", example = "张三")
|
||||
private String proposerNickname;
|
||||
|
||||
@Schema(description = "所需工时", example = "8")
|
||||
@NotNull(message = "所需工时不能为空")
|
||||
private Double workHours;
|
||||
|
||||
@Schema(description = "当前处理人用户 ID", example = "1024")
|
||||
private Long currentHandlerUserId;
|
||||
|
||||
@Schema(description = "当前处理人昵称", example = "李四")
|
||||
private String currentHandlerUserNickname;
|
||||
|
||||
@Schema(description = "排序值,越小越靠前", example = "0")
|
||||
private Integer sort;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 管理后台 - 项目需求拆分 Request VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 项目需求拆分 Request VO")
|
||||
@Data
|
||||
public class ProjectRequirementSplitReqVO {
|
||||
|
||||
@Schema(description = "父需求 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "父需求 ID 不能为空")
|
||||
private Long parentId;
|
||||
|
||||
@Schema(description = "所属项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "项目 ID 不能为空")
|
||||
private Long projectId;
|
||||
|
||||
@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 个字符")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "需求描述,支持富文本", example = "<p>详细描述需求内容</p>")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "需求分类字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "function")
|
||||
@NotBlank(message = "需求分类不能为空")
|
||||
@Size(max = 64, message = "需求分类长度不能超过 64 个字符")
|
||||
private String category;
|
||||
|
||||
@Schema(description = "优先级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "优先级不能为空")
|
||||
private Integer priority;
|
||||
|
||||
@Schema(description = "提出人用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "提出人不能为空")
|
||||
private Long proposerId;
|
||||
|
||||
@Schema(description = "提出人昵称", example = "张三")
|
||||
private String proposerNickname;
|
||||
|
||||
@Schema(description = "所需工时", example = "8")
|
||||
@NotNull(message = "所需工时不能为空")
|
||||
private Double workHours;
|
||||
|
||||
@Schema(description = "当前处理人用户 ID", example = "1024")
|
||||
private Long currentHandlerUserId;
|
||||
|
||||
@Schema(description = "当前处理人昵称", example = "李四")
|
||||
private String currentHandlerUserNickname;
|
||||
|
||||
@Schema(description = "排序值,越小越靠前", example = "0")
|
||||
private Integer sort;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 管理后台 - 项目需求状态变更 Request VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 项目需求状态变更 Request VO")
|
||||
@Data
|
||||
public class ProjectRequirementStatusActionReqVO {
|
||||
|
||||
@Schema(description = "需求 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "需求 ID 不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "项目 ID 不能为空")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "动作编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "pass_review")
|
||||
@NotBlank(message = "动作编码不能为空")
|
||||
@Size(max = 32, message = "动作编码长度不能超过 32 个字符")
|
||||
private String actionCode;
|
||||
|
||||
@Schema(description = "状态变更原因", example = "评审通过")
|
||||
private String reason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 管理后台 - 项目需求状态字典 Response VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 项目需求状态字典 Response VO")
|
||||
@Data
|
||||
public class ProjectRequirementStatusDictRespVO {
|
||||
|
||||
@Schema(description = "状态编码", example = "pending_confirm")
|
||||
private String statusCode;
|
||||
|
||||
@Schema(description = "状态名称", example = "待确认")
|
||||
private String statusName;
|
||||
|
||||
@Schema(description = "排序值", example = "1")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "是否初始状态", example = "true")
|
||||
private Boolean initialFlag;
|
||||
|
||||
@Schema(description = "是否终态", example = "false")
|
||||
private Boolean terminalFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 管理后台 - 项目需求状态可执行动作 Response VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 项目需求状态可执行动作 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ProjectRequirementStatusTransitionRespVO {
|
||||
|
||||
@Schema(description = "动作编码", example = "pass_review")
|
||||
private String actionCode;
|
||||
|
||||
@Schema(description = "动作名称", example = "评审通过")
|
||||
private String actionName;
|
||||
|
||||
@Schema(description = "目标状态编码", example = "implementing")
|
||||
private String toStatusCode;
|
||||
|
||||
@Schema(description = "目标状态名称", example = "实施中")
|
||||
private String toStatusName;
|
||||
|
||||
@Schema(description = "是否必须填写原因", example = "false")
|
||||
private Boolean needReason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 管理后台 - 项目需求编辑 Request VO
|
||||
*/
|
||||
@Schema(description = "管理后台 - 项目需求编辑 Request VO")
|
||||
@Data
|
||||
public class ProjectRequirementUpdateReqVO {
|
||||
|
||||
@Schema(description = "需求 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "需求 ID 不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "项目 ID 不能为空")
|
||||
private Long projectId;
|
||||
|
||||
@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 个字符")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "需求描述,支持富文本", example = "<p>详细描述需求内容</p>")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "需求分类字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "function")
|
||||
@NotBlank(message = "需求分类不能为空")
|
||||
@Size(max = 64, message = "需求分类长度不能超过 64 个字符")
|
||||
private String category;
|
||||
|
||||
@Schema(description = "优先级,0 低、1 中、2 高、3 紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "优先级不能为空")
|
||||
private Integer priority;
|
||||
|
||||
@Schema(description = "提出人用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "提出人不能为空")
|
||||
private Long proposerId;
|
||||
|
||||
@Schema(description = "提出人昵称", example = "张三")
|
||||
private String proposerNickname;
|
||||
|
||||
@Schema(description = "所需工时", example = "8")
|
||||
@NotNull(message = "所需工时不能为空")
|
||||
private Double workHours;
|
||||
|
||||
@Schema(description = "当前处理人用户 ID", example = "1024")
|
||||
private Long currentHandlerUserId;
|
||||
|
||||
@Schema(description = "当前处理人昵称", example = "李四")
|
||||
private String currentHandlerUserNickname;
|
||||
|
||||
@Schema(description = "排序值,越小越靠前", example = "0")
|
||||
private Integer sort;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 项目需求主表
|
||||
*/
|
||||
@TableName("rdms_project_requirement")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectRequirementDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键 ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 父需求 ID,0 表示顶级需求
|
||||
*/
|
||||
private Long parentId;
|
||||
/**
|
||||
* 所属项目 ID
|
||||
*/
|
||||
private Long projectId;
|
||||
/**
|
||||
* 所属模块 ID
|
||||
*/
|
||||
private Long moduleId;
|
||||
/**
|
||||
* 产品需求id
|
||||
*/
|
||||
private Long productRequirementId;
|
||||
/**
|
||||
* 是否需要评审,0 不需要,1 需要
|
||||
*/
|
||||
private Integer reviewRequired;
|
||||
/**
|
||||
* 需求标题
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* 需求描述,支持富文本
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 需求分类字典值
|
||||
*/
|
||||
private String category;
|
||||
/**
|
||||
* 来源类型
|
||||
*/
|
||||
private String sourceType;
|
||||
/**
|
||||
* 来源业务 ID
|
||||
*/
|
||||
private Long sourceBizId;
|
||||
/**
|
||||
* 优先级
|
||||
*/
|
||||
private Integer priority;
|
||||
/**
|
||||
* 当前状态编码
|
||||
*/
|
||||
private String statusCode;
|
||||
/**
|
||||
* 最近一次状态动作原因
|
||||
*/
|
||||
private String lastStatusReason;
|
||||
/**
|
||||
* 提出人用户 ID
|
||||
*/
|
||||
private Long proposerId;
|
||||
/**
|
||||
* 提出人昵称快照
|
||||
*/
|
||||
private String proposerNickname;
|
||||
/**
|
||||
* 当前处理人用户 ID
|
||||
*/
|
||||
private Long currentHandlerUserId;
|
||||
/**
|
||||
* 当前处理人昵称快照
|
||||
*/
|
||||
private String currentHandlerUserNickname;
|
||||
/**
|
||||
* 预估工时
|
||||
*/
|
||||
private Double workHours;
|
||||
/**
|
||||
* 排序值
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 项目需求模块表
|
||||
*/
|
||||
@TableName("rdms_project_requirement_module")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectRequirementModuleDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键 ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 父模块 ID,0 表示顶级模块
|
||||
*/
|
||||
private Long parentId;
|
||||
/**
|
||||
* 所属项目 ID
|
||||
*/
|
||||
private Long projectId;
|
||||
/**
|
||||
* 模块名称
|
||||
*/
|
||||
private String moduleName;
|
||||
/**
|
||||
* 模块说明
|
||||
*/
|
||||
private String remark;
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
private String icon;
|
||||
/**
|
||||
* 排序值
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 项目需求状态变更日志表
|
||||
*/
|
||||
@TableName("rdms_project_requirement_status_log")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectRequirementStatusLogDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键 ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 需求 ID
|
||||
*/
|
||||
private Long requirementId;
|
||||
/**
|
||||
* 动作编码
|
||||
*/
|
||||
private String actionType;
|
||||
/**
|
||||
* 变更前状态
|
||||
*/
|
||||
private String fromStatus;
|
||||
/**
|
||||
* 变更后状态
|
||||
*/
|
||||
private String toStatus;
|
||||
/**
|
||||
* 动作原因
|
||||
*/
|
||||
private String reason;
|
||||
/**
|
||||
* 操作人 ID
|
||||
*/
|
||||
private Long operatorUserId;
|
||||
/**
|
||||
* 操作人名称快照
|
||||
*/
|
||||
private String operatorName;
|
||||
/**
|
||||
* 需求标题快照
|
||||
*/
|
||||
private String requirementTitleSnapshot;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementPageReqVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectRequirementDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目需求 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProjectRequirementMapper extends BaseMapperX<ProjectRequirementDO> {
|
||||
|
||||
/**
|
||||
* 分页查询需求列表
|
||||
*/
|
||||
default PageResult<ProjectRequirementDO> selectPage(ProjectRequirementPageReqVO reqVO) {
|
||||
return selectPage(reqVO, buildQueryWrapper(reqVO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有符合条件的需求列表,用于树查询
|
||||
*/
|
||||
default List<ProjectRequirementDO> selectList(ProjectRequirementPageReqVO reqVO) {
|
||||
return selectList(buildQueryWrapper(reqVO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*/
|
||||
private LambdaQueryWrapperX<ProjectRequirementDO> buildQueryWrapper(ProjectRequirementPageReqVO reqVO) {
|
||||
LambdaQueryWrapperX<ProjectRequirementDO> queryWrapper = new LambdaQueryWrapperX<>();
|
||||
if (StringUtils.hasText(reqVO.getTitle())) {
|
||||
queryWrapper.like(ProjectRequirementDO::getTitle, reqVO.getTitle());
|
||||
}
|
||||
queryWrapper.eqIfPresent(ProjectRequirementDO::getCategory, reqVO.getCategory())
|
||||
.eqIfPresent(ProjectRequirementDO::getPriority, reqVO.getPriority())
|
||||
.eqIfPresent(ProjectRequirementDO::getStatusCode, reqVO.getStatusCode())
|
||||
.eqIfPresent(ProjectRequirementDO::getCurrentHandlerUserId, reqVO.getCurrentHandlerUserId())
|
||||
.eqIfPresent(ProjectRequirementDO::getSourceType, reqVO.getSourceType())
|
||||
.inIfPresent(ProjectRequirementDO::getModuleId, reqVO.getModuleIds())
|
||||
.eqIfPresent(ProjectRequirementDO::getModuleId, reqVO.getModuleId())
|
||||
.eqIfPresent(ProjectRequirementDO::getParentId, reqVO.getParentId())
|
||||
.eq(ProjectRequirementDO::getProjectId, reqVO.getProjectId())
|
||||
.orderByAsc(ProjectRequirementDO::getSort)
|
||||
.orderByDesc(ProjectRequirementDO::getCreateTime);
|
||||
return queryWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据父需求 ID 查询子需求列表
|
||||
*/
|
||||
default List<ProjectRequirementDO> selectListByParentId(Long parentId) {
|
||||
return selectList(new LambdaQueryWrapperX<ProjectRequirementDO>()
|
||||
.eq(ProjectRequirementDO::getParentId, parentId)
|
||||
.orderByAsc(ProjectRequirementDO::getSort)
|
||||
.orderByDesc(ProjectRequirementDO::getCreateTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模块 ID 查询需求列表
|
||||
*/
|
||||
default List<ProjectRequirementDO> selectListByModuleId(Long moduleId) {
|
||||
return selectList(new LambdaQueryWrapperX<ProjectRequirementDO>()
|
||||
.eq(ProjectRequirementDO::getModuleId, moduleId)
|
||||
.orderByAsc(ProjectRequirementDO::getSort));
|
||||
}
|
||||
|
||||
/**
|
||||
* 带并发控制的状态更新
|
||||
*/
|
||||
default int updateStatusByIdAndStatus(Long id, String fromStatus, String toStatus, String lastStatusReason) {
|
||||
ProjectRequirementDO update = new ProjectRequirementDO();
|
||||
update.setStatusCode(toStatus);
|
||||
update.setLastStatusReason(lastStatusReason);
|
||||
return update(update, new LambdaQueryWrapperX<ProjectRequirementDO>()
|
||||
.eq(ProjectRequirementDO::getId, id)
|
||||
.eq(ProjectRequirementDO::getStatusCode, fromStatus));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ID 和状态删除,带并发控制
|
||||
*/
|
||||
default int deleteByIdAndStatus(Long id, String statusCode) {
|
||||
return delete(new LambdaQueryWrapperX<ProjectRequirementDO>()
|
||||
.eq(ProjectRequirementDO::getId, id)
|
||||
.eq(ProjectRequirementDO::getStatusCode, statusCode));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project;
|
||||
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectRequirementModuleDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目需求模块 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProjectRequirementModuleMapper extends BaseMapperX<ProjectRequirementModuleDO> {
|
||||
|
||||
/**
|
||||
* 根据项目 ID 查询模块列表
|
||||
*/
|
||||
default List<ProjectRequirementModuleDO> selectListByProjectId(Long projectId) {
|
||||
return selectList(new LambdaQueryWrapperX<ProjectRequirementModuleDO>()
|
||||
.eq(ProjectRequirementModuleDO::getProjectId, projectId)
|
||||
.orderByAsc(ProjectRequirementModuleDO::getSort)
|
||||
.orderByAsc(ProjectRequirementModuleDO::getCreateTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据父模块 ID 查询子模块列表
|
||||
*/
|
||||
default List<ProjectRequirementModuleDO> selectListByParentId(Long parentId) {
|
||||
return selectList(new LambdaQueryWrapperX<ProjectRequirementModuleDO>()
|
||||
.eq(ProjectRequirementModuleDO::getParentId, parentId)
|
||||
.orderByAsc(ProjectRequirementModuleDO::getSort));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据项目 ID 和模块名称查询模块
|
||||
*/
|
||||
default ProjectRequirementModuleDO selectByProjectIdAndModuleName(Long projectId, String moduleName) {
|
||||
return selectOne(new LambdaQueryWrapperX<ProjectRequirementModuleDO>()
|
||||
.eq(ProjectRequirementModuleDO::getProjectId, projectId)
|
||||
.eq(ProjectRequirementModuleDO::getModuleName, moduleName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据项目 ID 和父模块 ID 查询模块
|
||||
*/
|
||||
default ProjectRequirementModuleDO selectByProjectIdAndParentId(Long projectId, Long parentId) {
|
||||
return selectOne(new LambdaQueryWrapperX<ProjectRequirementModuleDO>()
|
||||
.eq(ProjectRequirementModuleDO::getProjectId, projectId)
|
||||
.eq(ProjectRequirementModuleDO::getParentId, parentId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project;
|
||||
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectRequirementStatusLogDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目需求状态变更日志 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProjectRequirementStatusLogMapper extends BaseMapperX<ProjectRequirementStatusLogDO> {
|
||||
|
||||
/**
|
||||
* 根据需求 ID 查询状态变更日志列表
|
||||
*/
|
||||
default List<ProjectRequirementStatusLogDO> selectListByRequirementId(Long requirementId) {
|
||||
return selectList(new LambdaQueryWrapperX<ProjectRequirementStatusLogDO>()
|
||||
.eq(ProjectRequirementStatusLogDO::getRequirementId, requirementId)
|
||||
.orderByDesc(ProjectRequirementStatusLogDO::getCreateTime));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -88,6 +88,15 @@ public interface ProductRequirementService {
|
||||
*/
|
||||
List<ProductRequirementStatusTransitionRespVO> getAllowedTransitions(Long requirementId, Long productId);
|
||||
|
||||
/**
|
||||
* 判断需求是否已分流并生成项目需求
|
||||
*
|
||||
* @param requirementId 需求编号
|
||||
* @param productId 产品编号
|
||||
* @return 是否已分流
|
||||
*/
|
||||
boolean hasDispatchedProjectRequirement(Long requirementId, Long productId);
|
||||
|
||||
/**
|
||||
* 获取需求生命周期信息(当前状态 + 可执行动作)
|
||||
*
|
||||
@@ -144,4 +153,12 @@ public interface ProductRequirementService {
|
||||
*/
|
||||
List<ProductRequirementStatusDictRespVO> getRequirementTerminalStatusDict();
|
||||
|
||||
/**
|
||||
* 获取产品需求分流后对应的项目需求跳转链接
|
||||
*
|
||||
* @param productRequirementId 产品需求编号
|
||||
* @return 项目需求ID和实现项目ID
|
||||
*/
|
||||
ProductRequirementDispatchedProjectLinkRespVO getDispatchedProjectLink(Long productRequirementId);
|
||||
|
||||
}
|
||||
|
||||
@@ -4,18 +4,25 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.rdms.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.*;
|
||||
import com.njcn.rdms.module.project.constant.ProjectObjectConstants;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementModuleDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementStatusLogDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectRequirementDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectRequirementModuleDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO;
|
||||
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.product.ProductRequirementMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.product.ProductRequirementModuleMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.product.ProductRequirementStatusLogMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.project.ProjectRequirementMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.project.ProjectRequirementModuleMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper;
|
||||
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
|
||||
@@ -75,7 +82,13 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
private static final String ACTION_DELETE = "delete";
|
||||
private static final String ACTION_SPLIT = "split";
|
||||
private static final String ACTION_CLOSE = "close";
|
||||
private static final String ACTION_ACCEPT = "accept";
|
||||
private static final String ACTION_DISPATCH = "dispatch";
|
||||
private static final String ACTION_CANCEL = "cancel";
|
||||
private static final String ACTION_AUTO_DERIVE = "auto_derive";
|
||||
|
||||
private static final String BIZ_TYPE_REQUIREMENT = "product_requirement";
|
||||
private static final String AUTO_DERIVE_REASON = "根据子需求状态自动推导";
|
||||
|
||||
@Resource
|
||||
private ProductRequirementMapper requirementMapper;
|
||||
@@ -89,6 +102,12 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
private ObjectStatusTransitionMapper statusTransitionMapper;
|
||||
@Resource
|
||||
private ObjectStatusModelMapper statusModelMapper;
|
||||
@Resource
|
||||
private ProjectRequirementMapper projectRequirementMapper;
|
||||
@Resource
|
||||
private ProjectRequirementModuleMapper projectRequirementModuleMapper;
|
||||
@Resource
|
||||
private UserObjectRoleMapper userObjectRoleMapper;
|
||||
|
||||
// ========== 需求增删改查 ==========
|
||||
|
||||
@@ -190,8 +209,9 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
}
|
||||
}
|
||||
PageResult<ProductRequirementDO> pageResult = requirementMapper.selectPage(pageReqVO);
|
||||
Map<String, ObjectStatusModelDO> statusModelMap = getStatusModelMap();
|
||||
List<ProductRequirementRespVO> list = pageResult.getList().stream()
|
||||
.map(this::buildRequirementRespVO)
|
||||
.map(requirement -> buildRequirementRespVO(requirement, statusModelMap))
|
||||
.collect(Collectors.toList());
|
||||
return new PageResult<>(list, pageResult.getTotal());
|
||||
}
|
||||
@@ -217,12 +237,13 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
return new PageResult<>(Collections.emptyList(), 0L);
|
||||
}
|
||||
|
||||
Map<String, ObjectStatusModelDO> statusModelMap = getStatusModelMap();
|
||||
|
||||
// 第二步:找出所有匹配需求的根节点ID,同时收集路径上的所有节点ID
|
||||
Set<Long> rootIds = new HashSet<>();
|
||||
Set<Long> pathNodeIds = new HashSet<>();
|
||||
Map<Long, ProductRequirementDO> requirementCache = new HashMap<>();
|
||||
Map<Long, ProductRequirementDO> requirementCache = buildRequirementCacheWithAncestors(matchedRequirements);
|
||||
for (ProductRequirementDO req : matchedRequirements) {
|
||||
requirementCache.put(req.getId(), req);
|
||||
pathNodeIds.add(req.getId());
|
||||
Long rootId = findRootRequirementIdAndCollectPath(req, requirementCache, pathNodeIds);
|
||||
rootIds.add(rootId);
|
||||
@@ -250,10 +271,11 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
}
|
||||
|
||||
List<ProductRequirementDO> pagedRootRequirements = rootRequirements.subList(fromIndex, toIndex);
|
||||
Map<Long, List<ProductRequirementDO>> childrenMap = buildPathChildrenMap(pathNodeIds);
|
||||
|
||||
// 第五步:构建树形结构(只包含路径上的节点)
|
||||
List<ProductRequirementRespVO> list = pagedRootRequirements.stream()
|
||||
.map(req -> buildRequirementRespVOWithPathChildren(req, pathNodeIds))
|
||||
.map(req -> buildRequirementRespVOWithPathChildren(req, pathNodeIds, childrenMap, statusModelMap))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new PageResult<>(list, (long) total);
|
||||
@@ -261,57 +283,110 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
|
||||
/**
|
||||
* 向上追溯需求的根节点ID,同时收集路径上的所有节点ID
|
||||
*
|
||||
* @param requirement 起始需求
|
||||
* @param cache 需求缓存(避免重复查询)
|
||||
* @param cache 需求缓存(避免重复查询)
|
||||
* @param pathNodeIds 路径节点ID集合(输出参数)
|
||||
* @return 根节点ID(parentId = 0L 的需求ID)
|
||||
*/
|
||||
private Long findRootRequirementIdAndCollectPath(ProductRequirementDO requirement,
|
||||
Map<Long, ProductRequirementDO> cache,
|
||||
Set<Long> pathNodeIds) {
|
||||
if (requirement.getParentId() == null || requirement.getParentId() == 0L) {
|
||||
return requirement.getId();
|
||||
}
|
||||
// 从缓存中查找父需求,如果没有则查询数据库
|
||||
ProductRequirementDO parent = cache.get(requirement.getParentId());
|
||||
if (parent == null) {
|
||||
parent = requirementMapper.selectById(requirement.getParentId());
|
||||
if (parent != null) {
|
||||
cache.put(parent.getId(), parent);
|
||||
Map<Long, ProductRequirementDO> cache,
|
||||
Set<Long> pathNodeIds) {
|
||||
ProductRequirementDO current = requirement;
|
||||
while (current.getParentId() != null && current.getParentId() != 0L) {
|
||||
ProductRequirementDO parent = cache.get(current.getParentId());
|
||||
if (parent == null) {
|
||||
// 父需求缺失时,保持原有容错行为:就近把当前节点视为根节点
|
||||
return current.getId();
|
||||
}
|
||||
pathNodeIds.add(parent.getId());
|
||||
current = parent;
|
||||
}
|
||||
if (parent == null) {
|
||||
// 父需求不存在(数据异常),返回当前需求作为根
|
||||
return requirement.getId();
|
||||
}
|
||||
// 收集路径上的节点ID
|
||||
pathNodeIds.add(parent.getId());
|
||||
return findRootRequirementIdAndCollectPath(parent, cache, pathNodeIds);
|
||||
return current.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建需求响应VO(只包含路径上的子需求)
|
||||
*
|
||||
* @param requirement 需求
|
||||
* @param pathNodeIds 路径节点ID集合
|
||||
* @return 需求响应VO
|
||||
*/
|
||||
private ProductRequirementRespVO buildRequirementRespVOWithPathChildren(ProductRequirementDO requirement,
|
||||
Set<Long> pathNodeIds) {
|
||||
ProductRequirementRespVO respVO = buildRequirementRespVO(requirement);
|
||||
// 查询子需求
|
||||
List<ProductRequirementDO> allChildren = requirementMapper.selectListByParentId(requirement.getId());
|
||||
// 只保留路径上的子需求
|
||||
Set<Long> pathNodeIds,
|
||||
Map<Long, List<ProductRequirementDO>> childrenMap,
|
||||
Map<String, ObjectStatusModelDO> statusModelMap) {
|
||||
ProductRequirementRespVO respVO = buildRequirementRespVO(requirement, statusModelMap);
|
||||
List<ProductRequirementDO> allChildren = childrenMap.getOrDefault(requirement.getId(), Collections.emptyList());
|
||||
List<ProductRequirementDO> pathChildren = allChildren.stream()
|
||||
.filter(child -> pathNodeIds.contains(child.getId()))
|
||||
.toList();
|
||||
if (!pathChildren.isEmpty()) {
|
||||
respVO.setChildren(pathChildren.stream()
|
||||
.map(child -> buildRequirementRespVOWithPathChildren(child, pathNodeIds))
|
||||
.map(child -> buildRequirementRespVOWithPathChildren(child, pathNodeIds, childrenMap, statusModelMap))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return respVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量补齐命中需求向上的祖先链,避免树查询逐条回库查父节点。
|
||||
*/
|
||||
private Map<Long, ProductRequirementDO> buildRequirementCacheWithAncestors(List<ProductRequirementDO> matchedRequirements) {
|
||||
Map<Long, ProductRequirementDO> cache = matchedRequirements.stream()
|
||||
.collect(Collectors.toMap(ProductRequirementDO::getId, Function.identity(), (left, right) -> left, HashMap::new));
|
||||
|
||||
Set<Long> pendingParentIds = matchedRequirements.stream()
|
||||
.map(ProductRequirementDO::getParentId)
|
||||
.filter(parentId -> parentId != null && parentId != 0L && !cache.containsKey(parentId))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
while (!pendingParentIds.isEmpty()) {
|
||||
List<ProductRequirementDO> parentRequirements = requirementMapper.selectBatchIds(pendingParentIds);
|
||||
if (parentRequirements.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
Set<Long> nextPendingParentIds = new HashSet<>();
|
||||
for (ProductRequirementDO parentRequirement : parentRequirements) {
|
||||
cache.putIfAbsent(parentRequirement.getId(), parentRequirement);
|
||||
Long parentId = parentRequirement.getParentId();
|
||||
if (parentId != null && parentId != 0L && !cache.containsKey(parentId)) {
|
||||
nextPendingParentIds.add(parentId);
|
||||
}
|
||||
}
|
||||
pendingParentIds = nextPendingParentIds;
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量加载路径节点的直属子需求,后续仅在内存中组装树结构。
|
||||
*/
|
||||
private Map<Long, List<ProductRequirementDO>> buildPathChildrenMap(Set<Long> pathNodeIds) {
|
||||
if (pathNodeIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<ProductRequirementDO> pathChildren = requirementMapper.selectList(
|
||||
new LambdaQueryWrapperX<ProductRequirementDO>()
|
||||
.in(ProductRequirementDO::getParentId, pathNodeIds)
|
||||
.orderByAsc(ProductRequirementDO::getSort)
|
||||
.orderByDesc(ProductRequirementDO::getCreateTime));
|
||||
Map<Long, List<ProductRequirementDO>> childrenMap = new HashMap<>();
|
||||
for (ProductRequirementDO child : pathChildren) {
|
||||
childrenMap.computeIfAbsent(child.getParentId(), key -> new ArrayList<>()).add(child);
|
||||
}
|
||||
return childrenMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次性加载当前对象类型下的状态模型,供列表和树查询复用。
|
||||
*/
|
||||
private Map<String, ObjectStatusModelDO> getStatusModelMap() {
|
||||
return statusModelMapper.selectListByObjectType(REQUIREMENT_OBJECT_TYPE).stream()
|
||||
.collect(Collectors.toMap(ObjectStatusModelDO::getStatusCode, Function.identity(),
|
||||
(left, right) -> left, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定模块是否为"全部需求"模块(parentId = 0L 的根模块)
|
||||
*/
|
||||
@@ -326,7 +401,8 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
|
||||
/**
|
||||
* 递归获取模块及其所有子模块的ID列表
|
||||
* @param moduleId 起始模块ID
|
||||
*
|
||||
* @param moduleId 起始模块ID
|
||||
* @param productId 产品ID
|
||||
* @return 包含自身及所有子模块的ID列表
|
||||
*/
|
||||
@@ -355,6 +431,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
|
||||
/**
|
||||
* 递归获取需求及其所有子需求(包含子子需求)
|
||||
*
|
||||
* @param requirementId 起始需求ID
|
||||
* @return 包含自身及所有后代需求的列表
|
||||
*/
|
||||
@@ -398,17 +475,21 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
String toStatus = transition.getToStatusCode();
|
||||
|
||||
// accept和close动作时,校验所有子需求(包括子子需求)是否处于允许状态
|
||||
if ("accept".equals(actionCode) || "close".equals(actionCode)) {
|
||||
if (ACTION_ACCEPT.equals(actionCode) || ACTION_CLOSE.equals(actionCode)) {
|
||||
validateAllChildrenAllowCloseOrAccept(reqVO.getId());
|
||||
}
|
||||
// cancel动作时,如果是父需求则校验所有子需求是否处于已拒绝或已取消
|
||||
if ("cancel".equals(actionCode) && Objects.equals(requirement.getParentId(), 0L)) {
|
||||
// cancel动作时,只要存在子需求就按父需求规则校验
|
||||
if (ACTION_CANCEL.equals(actionCode) && hasChildren(requirement.getId())) {
|
||||
validateParentCancelAllowed(reqVO.getId());
|
||||
}
|
||||
// close动作时,递归关闭所有已验收的子需求(包括子子需求)
|
||||
if ("close".equals(actionCode)) {
|
||||
if (ACTION_CLOSE.equals(actionCode)) {
|
||||
closeAllAcceptedChildren(reqVO.getId(), reason);
|
||||
}
|
||||
// dispatch动作且选择了实现项目时,自动创建对应的项目需求
|
||||
if (ACTION_DISPATCH.equals(actionCode) && implementProjectId != null) {
|
||||
createProjectRequirementFromProduct(requirement, implementProjectId);
|
||||
}
|
||||
// 带并发控制的状态更新(支持同时更新实现项目ID)
|
||||
int updateCount = requirementMapper.updateStatusByIdAndStatusWithProject(requirement.getId(), fromStatus, toStatus, reason, implementProjectId);
|
||||
if (updateCount != 1) {
|
||||
@@ -425,6 +506,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
// 写入业务审计日志
|
||||
writeBizAuditLog(requirement, actionCode, fromStatus, toStatus,
|
||||
buildRequirementFieldChanges(before, requirement), reason);
|
||||
refreshAncestorStatusRecursively(requirement.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -496,6 +578,8 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
public Long splitRequirement(ProductRequirementSplitReqVO reqVO) {
|
||||
// 校验父需求是否存在
|
||||
ProductRequirementDO parentRequirement = validateRequirementExists(reqVO.getParentId());
|
||||
// 产品需求一旦已分流生成项目需求,就只能到项目需求侧继续拆分
|
||||
validateRequirementNotDispatched(parentRequirement);
|
||||
// 校验父需求状态是否允许拆分(只能是待分流或实施中)
|
||||
validateParentAllowSplit(parentRequirement);
|
||||
|
||||
@@ -577,6 +661,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
writeRequirementStatusLog(requirement, ACTION_CLOSE, fromStatus, STATUS_CLOSED, reason);
|
||||
writeBizAuditLog(requirement, ACTION_CLOSE, fromStatus, STATUS_CLOSED,
|
||||
buildRequirementFieldChanges(before, requirement), reason);
|
||||
refreshAncestorStatusRecursively(requirement.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -608,13 +693,21 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public List<ProductRequirementStatusTransitionRespVO> getAllowedTransitions(Long requirementId, Long productId) {
|
||||
ProductRequirementDO requirement = validateRequirementExists(requirementId);
|
||||
// 当产品需求已分流并生成项目需求后,产品需求端不再返回动作按钮
|
||||
if (hasDispatchedProjectRequirement(requirement)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String currentStatus = requirement.getStatusCode();
|
||||
|
||||
// 查询当前状态允许的所有流转
|
||||
List<ObjectStatusTransitionDO> transitions = statusTransitionMapper
|
||||
.selectListByObjectTypeAndFromStatus(REQUIREMENT_OBJECT_TYPE, currentStatus);
|
||||
|
||||
return transitions.stream().map(transition -> {
|
||||
return transitions.stream()
|
||||
// 取消动作不满足前置条件时,不再返回给前端展示按钮
|
||||
.filter(transition -> shouldExposeTransition(requirement, transition))
|
||||
.map(transition -> {
|
||||
ProductRequirementStatusTransitionRespVO vo = new ProductRequirementStatusTransitionRespVO();
|
||||
vo.setActionCode(transition.getActionCode());
|
||||
vo.setActionName(transition.getActionName());
|
||||
@@ -629,6 +722,23 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
}
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public boolean hasDispatchedProjectRequirement(Long requirementId, Long productId) {
|
||||
ProductRequirementDO requirement = validateRequirementExists(requirementId);
|
||||
return hasDispatchedProjectRequirement(requirement);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该方法作用和getAllowedTransitions()类似,是用来获取当前状态下可以进行的动作
|
||||
* @deprecated 产品需求页面最开始用来下拉框改状态时使用的,已经弃用
|
||||
*
|
||||
* @param requirementId 需求编号
|
||||
* @param productId 产品编号
|
||||
* @return ProductRequirementLifecycleRespVO
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public ProductRequirementLifecycleRespVO getRequirementLifecycle(Long requirementId, Long productId) {
|
||||
@@ -649,9 +759,176 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
lifecycle.setAllowEdit(statusModel.getAllowEdit());
|
||||
lifecycle.setLastStatusReason(requirement.getLastStatusReason());
|
||||
lifecycle.setAvailableActions(getAllowedTransitions(requirementId, productId));
|
||||
|
||||
return lifecycle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProductRequirementDispatchedProjectLinkRespVO getDispatchedProjectLink(Long productRequirementId) {
|
||||
// 校验产品需求是否存在,以及是否已分流到具体的实现项目
|
||||
ProductRequirementDO requirement = validateRequirementExists(productRequirementId);
|
||||
if (requirement.getImplementProjectId() == null) {
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_NOT_DISPATCHED);
|
||||
}
|
||||
Long projectId = requirement.getImplementProjectId();
|
||||
|
||||
// 查询产品需求分流后生成的顶级项目需求
|
||||
List<ProjectRequirementDO> projectRequirements = projectRequirementMapper.selectList(
|
||||
new LambdaQueryWrapperX<ProjectRequirementDO>()
|
||||
.eq(ProjectRequirementDO::getProductRequirementId, productRequirementId)
|
||||
.eq(ProjectRequirementDO::getParentId, 0L)
|
||||
);
|
||||
if (projectRequirements.isEmpty()) {
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_DISPATCHED_PROJECT_REQUIREMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 校验当前登录用户是否为该项目的成员
|
||||
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||
if (userObjectRoleMapper.selectActiveByObjectAndUserId(
|
||||
ProjectObjectConstants.OBJECT_TYPE, projectId, loginUserId) == null) {
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_NOT_PROJECT_MEMBER);
|
||||
}
|
||||
|
||||
ProductRequirementDispatchedProjectLinkRespVO respVO = new ProductRequirementDispatchedProjectLinkRespVO();
|
||||
respVO.setProjectRequirementId(projectRequirements.get(0).getId());
|
||||
respVO.setProjectId(projectId);
|
||||
return respVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断产品需求是否已成功分流并生成对应的项目需求
|
||||
*/
|
||||
public boolean hasDispatchedProjectRequirement(ProductRequirementDO requirement) {
|
||||
if (requirement.getImplementProjectId() == null) {
|
||||
return false;
|
||||
}
|
||||
List<ProjectRequirementDO> projectRequirements = projectRequirementMapper.selectList(
|
||||
new LambdaQueryWrapperX<ProjectRequirementDO>()
|
||||
.eq(ProjectRequirementDO::getSourceType, "product_requirement")
|
||||
.eq(ProjectRequirementDO::getProductRequirementId, requirement.getId())
|
||||
);
|
||||
return !projectRequirements.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 已分流并生成项目需求后,产品需求端不再允许继续拆分。
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void validateRequirementNotDispatched(ProductRequirementDO requirement) {
|
||||
if (hasDispatchedProjectRequirement(requirement)) {
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_DISPATCHED_NOT_ALLOW_SPLIT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前只对取消动作做额外过滤,避免前端展示点了也会报错的按钮。
|
||||
*/
|
||||
private boolean shouldExposeTransition(ProductRequirementDO requirement, ObjectStatusTransitionDO transition) {
|
||||
if (!ACTION_CANCEL.equals(transition.getActionCode())) {
|
||||
return true;
|
||||
}
|
||||
if (!hasChildren(requirement.getId())) {
|
||||
return true;
|
||||
}
|
||||
return isParentCancelAllowed(requirement.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 父需求存在子需求时,只有全部子需求都已取消或已拒绝,才允许展示取消动作。
|
||||
*/
|
||||
private boolean isParentCancelAllowed(Long requirementId) {
|
||||
List<ProductRequirementDO> allChildren = getAllRequirementsWithChildren(requirementId);
|
||||
for (ProductRequirementDO req : allChildren) {
|
||||
if (!Objects.equals(req.getId(), requirementId)
|
||||
&& !CHILD_ALLOW_CANCEL_STATUSES.contains(req.getStatusCode())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只要存在直接子需求,就应按父需求的取消规则处理。
|
||||
*/
|
||||
private boolean hasChildren(Long requirementId) {
|
||||
return !requirementMapper.selectListByParentId(requirementId).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 子需求状态变化后,仅向上自动推导产品需求父节点状态,不涉及跨对象回写。
|
||||
*/
|
||||
private void refreshAncestorStatusRecursively(Long requirementId) {
|
||||
ProductRequirementDO currentRequirement = requirementMapper.selectById(requirementId);
|
||||
if (currentRequirement == null || currentRequirement.getParentId() == null
|
||||
|| Objects.equals(currentRequirement.getParentId(), 0L)) {
|
||||
return;
|
||||
}
|
||||
ProductRequirementDO parentRequirement = requirementMapper.selectById(currentRequirement.getParentId());
|
||||
if (parentRequirement == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String targetStatus = deriveParentStatusByDirectChildren(parentRequirement.getId());
|
||||
if (!StringUtils.hasText(targetStatus) || Objects.equals(targetStatus, parentRequirement.getStatusCode())) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProductRequirementDO before = cloneRequirement(parentRequirement);
|
||||
int updateCount = requirementMapper.updateStatusByIdAndStatus(parentRequirement.getId(),
|
||||
parentRequirement.getStatusCode(), targetStatus, AUTO_DERIVE_REASON);
|
||||
if (updateCount != 1) {
|
||||
throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_CONCURRENT_MODIFIED);
|
||||
}
|
||||
parentRequirement.setStatusCode(targetStatus);
|
||||
parentRequirement.setLastStatusReason(AUTO_DERIVE_REASON);
|
||||
|
||||
writeRequirementStatusLog(parentRequirement, ACTION_AUTO_DERIVE, before.getStatusCode(),
|
||||
targetStatus, AUTO_DERIVE_REASON);
|
||||
writeBizAuditLog(parentRequirement, ACTION_AUTO_DERIVE, before.getStatusCode(),
|
||||
targetStatus, buildRequirementFieldChanges(before, parentRequirement), AUTO_DERIVE_REASON);
|
||||
|
||||
refreshAncestorStatusRecursively(parentRequirement.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 父需求状态只在约定好的几种场景下自动变化,其他组合保持当前状态不变:
|
||||
* accepted 全量汇总为 accepted,closed 全量汇总为 closed,
|
||||
* cancelled 全量汇总为 cancelled,accepted/closed 混合且至少一个 accepted 时汇总为 accepted。
|
||||
*/
|
||||
private String deriveParentStatusByDirectChildren(Long parentRequirementId) {
|
||||
List<ProductRequirementDO> children = requirementMapper.selectListByParentId(parentRequirementId);
|
||||
if (children.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean allAccepted = children.stream()
|
||||
.allMatch(child -> STATUS_ACCEPTED.equals(child.getStatusCode()));
|
||||
if (allAccepted) {
|
||||
return STATUS_ACCEPTED;
|
||||
}
|
||||
|
||||
boolean allClosed = children.stream()
|
||||
.allMatch(child -> STATUS_CLOSED.equals(child.getStatusCode()));
|
||||
if (allClosed) {
|
||||
return STATUS_CLOSED;
|
||||
}
|
||||
|
||||
boolean allCancelled = children.stream()
|
||||
.allMatch(child -> STATUS_CANCELLED.equals(child.getStatusCode()));
|
||||
if (allCancelled) {
|
||||
return STATUS_CANCELLED;
|
||||
}
|
||||
|
||||
boolean allAcceptedOrClosed = children.stream().allMatch(child ->
|
||||
STATUS_ACCEPTED.equals(child.getStatusCode()) || STATUS_CLOSED.equals(child.getStatusCode()));
|
||||
boolean hasAccepted = children.stream()
|
||||
.anyMatch(child -> STATUS_ACCEPTED.equals(child.getStatusCode()));
|
||||
if (allAcceptedOrClosed && hasAccepted) {
|
||||
return STATUS_ACCEPTED;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========== 模块管理 ==========
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -758,10 +1035,16 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
* 构建需求响应VO(不含子需求)
|
||||
*/
|
||||
private ProductRequirementRespVO buildRequirementRespVO(ProductRequirementDO requirement) {
|
||||
return buildRequirementRespVO(requirement, getStatusModelMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* 复用已加载的状态模型构建需求响应,避免列表场景重复查状态字典。
|
||||
*/
|
||||
private ProductRequirementRespVO buildRequirementRespVO(ProductRequirementDO requirement,
|
||||
Map<String, ObjectStatusModelDO> statusModelMap) {
|
||||
ProductRequirementRespVO respVO = BeanUtils.toBean(requirement, ProductRequirementRespVO.class);
|
||||
// 查询状态名称
|
||||
ObjectStatusModelDO statusModel = statusModelMapper
|
||||
.selectByObjectTypeAndStatusCode(REQUIREMENT_OBJECT_TYPE, requirement.getStatusCode());
|
||||
ObjectStatusModelDO statusModel = statusModelMap.get(requirement.getStatusCode());
|
||||
if (statusModel != null) {
|
||||
respVO.setStatusName(statusModel.getStatusName());
|
||||
respVO.setTerminal(statusModel.getTerminalFlag());
|
||||
@@ -1058,4 +1341,53 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
|
||||
return StringUtils.hasText(value) ? value : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* dispatch动作且已选择实现项目时,自动将产品需求转化为项目需求
|
||||
*
|
||||
* @param productRequirement 产品需求
|
||||
* @param implementProjectId 实现项目ID
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void createProjectRequirementFromProduct(ProductRequirementDO productRequirement, Long implementProjectId) {
|
||||
List<ProjectRequirementDO> existingRequirements = projectRequirementMapper.selectList(
|
||||
new LambdaQueryWrapperX<ProjectRequirementDO>()
|
||||
.eq(ProjectRequirementDO::getSourceType, "product_requirement")
|
||||
.eq(ProjectRequirementDO::getProductRequirementId, productRequirement.getId())
|
||||
);
|
||||
if (!existingRequirements.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询实现项目下的根模块(parentId = 0)
|
||||
ProjectRequirementModuleDO rootModule = projectRequirementModuleMapper.selectByProjectIdAndParentId(implementProjectId, 0L);
|
||||
if (rootModule == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_REQUIREMENT_MODULE_ROOT_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 构建项目需求记录
|
||||
ProjectRequirementDO newRequirement = new ProjectRequirementDO();
|
||||
newRequirement.setParentId(0L); // 顶级需求
|
||||
newRequirement.setProjectId(implementProjectId);
|
||||
newRequirement.setModuleId(rootModule.getId());
|
||||
newRequirement.setProductRequirementId(productRequirement.getId()); // 溯源关系
|
||||
newRequirement.setSourceType("product_requirement");
|
||||
newRequirement.setStatusCode(STATUS_IMPLEMENTING);
|
||||
newRequirement.setReviewRequired(0); //从产品需求流转到项目需求的需求肯定不需要评审
|
||||
// 拷贝产品需求的其他字段(不拷贝排序、状态原因、更新人、更新时间、逻辑删除字段)
|
||||
newRequirement.setTitle(productRequirement.getTitle());
|
||||
newRequirement.setDescription(productRequirement.getDescription());
|
||||
newRequirement.setCategory(productRequirement.getCategory());
|
||||
newRequirement.setSourceBizId(productRequirement.getSourceBizId());
|
||||
newRequirement.setPriority(productRequirement.getPriority());
|
||||
newRequirement.setProposerId(productRequirement.getProposerId());
|
||||
newRequirement.setProposerNickname(productRequirement.getProposerNickname());
|
||||
newRequirement.setCurrentHandlerUserId(productRequirement.getCurrentHandlerUserId());
|
||||
newRequirement.setCurrentHandlerUserNickname(productRequirement.getCurrentHandlerUserNickname());
|
||||
newRequirement.setWorkHours(productRequirement.getWorkHours());
|
||||
newRequirement.setCreator(productRequirement.getCreator());
|
||||
newRequirement.setCreateTime(productRequirement.getCreateTime());
|
||||
|
||||
projectRequirementMapper.insert(newRequirement);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.njcn.rdms.module.project.service.project;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementCloseReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementLifecycleRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementModuleReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementModuleRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementSaveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementSplitReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementStatusActionReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementStatusDictRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementStatusTransitionRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementUpdateReqVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目需求 Service 接口
|
||||
*/
|
||||
public interface ProjectRequirementService {
|
||||
|
||||
/**
|
||||
* 创建项目需求
|
||||
*/
|
||||
Long createRequirement(ProjectRequirementSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新项目需求
|
||||
*/
|
||||
void updateRequirement(ProjectRequirementUpdateReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 获取需求详情
|
||||
*/
|
||||
ProjectRequirementRespVO getRequirement(Long id, Long projectId);
|
||||
|
||||
/**
|
||||
* 获取需求分页列表
|
||||
*/
|
||||
PageResult<ProjectRequirementRespVO> getRequirementPage(ProjectRequirementPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获取需求树形列表
|
||||
*/
|
||||
PageResult<ProjectRequirementRespVO> getRequirementTree(ProjectRequirementPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 变更需求状态
|
||||
*/
|
||||
void changeRequirementStatus(ProjectRequirementStatusActionReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 删除需求
|
||||
*/
|
||||
void deleteRequirement(Long id, Long projectId);
|
||||
|
||||
/**
|
||||
* 拆分需求
|
||||
*/
|
||||
Long splitRequirement(ProjectRequirementSplitReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 关闭需求
|
||||
*/
|
||||
void closeRequirement(ProjectRequirementCloseReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 获取需求可执行动作列表
|
||||
*/
|
||||
List<ProjectRequirementStatusTransitionRespVO> getAllowedTransitions(Long requirementId, Long projectId);
|
||||
|
||||
/**
|
||||
* 获取需求生命周期信息
|
||||
*/
|
||||
ProjectRequirementLifecycleRespVO getRequirementLifecycle(Long requirementId, Long projectId);
|
||||
|
||||
/**
|
||||
* 创建需求模块
|
||||
*/
|
||||
Long createRequirementModule(ProjectRequirementModuleReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 更新需求模块
|
||||
*/
|
||||
void updateRequirementModule(ProjectRequirementModuleReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 删除需求模块
|
||||
*/
|
||||
void deleteRequirementModule(Long moduleId, Long projectId);
|
||||
|
||||
/**
|
||||
* 获取需求模块树
|
||||
*/
|
||||
List<ProjectRequirementModuleRespVO> getRequirementModuleTree(Long projectId);
|
||||
|
||||
/**
|
||||
* 获取需求状态字典
|
||||
*/
|
||||
List<ProjectRequirementStatusDictRespVO> getRequirementStatusDict();
|
||||
|
||||
/**
|
||||
* 获取需求终态字典
|
||||
*/
|
||||
List<ProjectRequirementStatusDictRespVO> getRequirementTerminalStatusDict();
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user