From 10b7ccdeb0fe5f01083801875eda4da9fd05bbc9 Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Mon, 8 Jun 2026 17:17:18 +0800 Subject: [PATCH] =?UTF-8?q?refactor(project):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=9D=83=E9=99=90=E5=B8=B8=E9=87=8F=E5=AE=9A=E4=B9=89=E5=B9=B6?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E9=9C=80=E6=B1=82=E8=BF=9B=E5=BA=A6=E8=81=9A?= =?UTF-8?q?=E5=90=88=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将产品和项目查询权限码统一提取到常量类中 - 移除需求进度聚合计算的相关实现代码 - 更新权限验证注解使用新的常量定义 - 清理相关的单元测试代码 - 更新错误码注释说明 --- .../project/enums/ErrorCodeConstants.java | 3 +- .../constant/ProductObjectConstants.java | 5 + .../constant/ProjectObjectConstants.java | 5 + .../requirement/ProjectRequirementRespVO.java | 6 +- .../ProductRequirementServiceImpl.java | 8 +- .../ProjectRequirementServiceImpl.java | 123 +---------------- .../review/RequirementReviewServiceImpl.java | 4 +- .../ProjectRequirementServiceImplTest.java | 126 ------------------ 8 files changed, 20 insertions(+), 260 deletions(-) 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 d3ff411..e92172f 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 @@ -128,8 +128,7 @@ public interface ErrorCodeConstants { ErrorCode PROJECT_EXECUTION_ASSIGNEE_ALREADY_EXISTS = new ErrorCode(1_008_003_004, "该用户已是当前执行的有效协办人"); ErrorCode PROJECT_EXECUTION_ASSIGNEE_NOT_EXISTS = new ErrorCode(1_008_003_005, "执行协办人不存在"); ErrorCode PROJECT_EXECUTION_ASSIGNEE_NOT_ACTIVE = new ErrorCode(1_008_003_006, "当前执行协办人已失效"); - // 保留:TD-013 解锁后业务路径已不会再触发,预留用于灰度回滚关闭关联能力 - ErrorCode PROJECT_EXECUTION_REQUIREMENT_NOT_READY = new ErrorCode(1_008_003_007, "当前阶段不支持给执行绑定项目需求"); + // 1_008_003_007 原 PROJECT_EXECUTION_REQUIREMENT_NOT_READY 已随 TD-013 清理删除,号位保留空缺不复用,避免与历史前端映射冲突 ErrorCode PROJECT_EXECUTION_NOT_ALLOW_EDIT = new ErrorCode(1_008_003_008, "当前项目状态不允许维护执行"); ErrorCode PROJECT_EXECUTION_OWNER_HANDOFF_REQUIRED = new ErrorCode(1_008_003_009, "该项目成员仍担任未终态执行负责人,请先完成执行负责人交接"); ErrorCode PROJECT_EXECUTION_STATUS_MODEL_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_008_003_010, "执行状态定义不存在或已停用"); diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/ProductObjectConstants.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/ProductObjectConstants.java index 54a03d6..a4cd2a9 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/ProductObjectConstants.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/ProductObjectConstants.java @@ -28,6 +28,11 @@ public final class ProductObjectConstants { */ public static final String CODE_PREFIX = "CNPD"; + /** + * 产品查询权限码。 + */ + public static final String PERMISSION_QUERY = "project:product:query"; + /** * 产品编辑权限码。 */ diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/ProjectObjectConstants.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/ProjectObjectConstants.java index 8797db9..b2539fd 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/ProjectObjectConstants.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/ProjectObjectConstants.java @@ -40,6 +40,11 @@ public final class ProjectObjectConstants { */ public static final String CODE_PREFIX = "CNPJ"; + /** + * 项目查询权限码。 + */ + public static final String PERMISSION_QUERY = "project:project:query"; + /** * 项目编辑权限码。 */ diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/vo/requirement/ProjectRequirementRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/vo/requirement/ProjectRequirementRespVO.java index 72b762b..c3aa059 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/vo/requirement/ProjectRequirementRespVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/vo/requirement/ProjectRequirementRespVO.java @@ -97,10 +97,8 @@ public class ProjectRequirementRespVO { @Schema(description = "是否为终态", example = "false") private Boolean terminal; - @Schema(description = "需求进度(TD-016 读时聚合,service 层批量计算)。" - + "公式:AVG(该需求自己承接的执行进度 ∪ 直接子需求进度)," - + "排除 rdms_object_status_model.progress_excluded_flag=1 的执行状态(当前为 cancelled);" - + "无任何执行且无子需求时返回 0.00。两位小数,HALF_UP。", + @Schema(description = "需求进度。TD-016:读时聚合计算已下线(前端当前不展示需求进度,进度仅项目/执行/任务展示)," + + "字段保留占位、当前恒为 null;未来需要展示需求进度时再恢复服务端聚合计算。", example = "0.65") private BigDecimal progressRate; 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 22c04d5..0ab6c47 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 @@ -76,11 +76,11 @@ public class ProductRequirementServiceImpl implements ProductRequirementService // 权限常量 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_QUERY_PERMISSION = ProductObjectConstants.PERMISSION_QUERY; + private static final String PRODUCT_UPDATE_PERMISSION = ProductObjectConstants.PERMISSION_UPDATE; + private static final String PRODUCT_STATUS_PERMISSION = ProductObjectConstants.PERMISSION_STATUS; private static final String PRODUCT_REVIEW_PERMISSION = ProductObjectConstants.PERMISSION_REVIEW; - private static final String PRODUCT_DELETE_PERMISSION = "project:product:delete"; + private static final String PRODUCT_DELETE_PERMISSION = ProductObjectConstants.PERMISSION_DELETE; private static final String PRODUCT_SPLIT_PERMISSION = "project:product:split"; // 审计动作常量 diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/project/ProjectRequirementServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/project/ProjectRequirementServiceImpl.java index f66fc86..a759e63 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/project/ProjectRequirementServiceImpl.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/project/ProjectRequirementServiceImpl.java @@ -88,7 +88,7 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService STATUS_REJECTED, STATUS_CANCELLED); private static final String PROJECT_CREATE_PERMISSION = "project:project:create"; - private static final String PROJECT_QUERY_PERMISSION = "project:project:query"; + private static final String PROJECT_QUERY_PERMISSION = ProjectObjectConstants.PERMISSION_QUERY; private static final String PROJECT_UPDATE_PERMISSION = ProjectObjectConstants.PERMISSION_UPDATE; private static final String PROJECT_STATUS_PERMISSION = ProjectObjectConstants.PERMISSION_STATUS; private static final String PROJECT_REVIEW_PERMISSION = ProjectObjectConstants.PERMISSION_REVIEW; @@ -219,7 +219,6 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService ProjectRequirementDO requirement = validateRequirementExists(id); validateRequirementBelongsToProject(requirement, projectId); ProjectRequirementRespVO respVO = buildRequirementRespVO(requirement); - fillRequirementProgress(projectId, List.of(respVO)); return respVO; } @@ -241,7 +240,6 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService List list = pageResult.getList().stream() .map(requirement -> buildRequirementRespVO(requirement, statusModelMap)) .collect(Collectors.toList()); - fillRequirementProgress(pageReqVO.getProjectId(), list); return new PageResult<>(list, pageResult.getTotal()); } @@ -296,7 +294,6 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService List list = pagedRootRequirements.stream() .map(requirement -> buildRequirementRespVOWithPathChildren(requirement, pathNodeIds, childrenMap, statusModelMap)) .collect(Collectors.toList()); - fillRequirementProgress(projectId, list); return new PageResult<>(list, (long) total); } @@ -1134,124 +1131,6 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService return respVO; } - /** - * TD-016:把项目需求 RespVO 列表上的 progressRate 字段批量回填。 - *

口径:按 projectId 一次性算出本项目下所有需求的进度 map,再递归扫 RespVO 树(含 children)应用。 - * 公式:R.progressRate = AVG(R 自己挂的执行进度 ∪ R 的直接子需求进度),排除进度排除字典命中的执行状态; - * 当 pool 为空(无承接执行且无子需求)时返回 0.00。两位小数 HALF_UP。 - */ - private void fillRequirementProgress(Long projectId, Collection respVOList) { - if (respVOList == null || respVOList.isEmpty() || projectId == null) { - return; - } - Map progressMap = computeRequirementProgressMapByProjectId(projectId); - applyProgressRecursive(respVOList, progressMap); - } - - private void applyProgressRecursive(Collection list, Map progressMap) { - BigDecimal defaultValue = normalizeProgress(BigDecimal.ZERO); - for (ProjectRequirementRespVO vo : list) { - vo.setProgressRate(progressMap.getOrDefault(vo.getId(), defaultValue)); - if (vo.getChildren() != null && !vo.getChildren().isEmpty()) { - applyProgressRecursive(vo.getChildren(), progressMap); - } - } - } - - /** - * 按 projectId 算所有需求进度,返回 Map<requirementId, progressRate>。 - * 公式与 fillRequirementProgress 对齐:自下而上 DFS,pool = 自己挂的执行平均 ∪ 直接子需求进度。 - */ - @VisibleForTesting - Map computeRequirementProgressMapByProjectId(Long projectId) { - List allRequirements = requirementMapper.selectList( - new LambdaQueryWrapperX() - .eq(ProjectRequirementDO::getProjectId, projectId)); - if (allRequirements.isEmpty()) { - return Collections.emptyMap(); - } - Set requirementIds = allRequirements.stream() - .map(ProjectRequirementDO::getId) - .collect(Collectors.toCollection(LinkedHashSet::new)); - - // 一次 GROUP BY 拉到所有需求"自己挂的执行平均进度" - List excludedStatusCodes = loadProgressExcludedExecutionStatusCodes(); - Map ownAvgMap = new HashMap<>(); - List> rows = projectExecutionMapper - .selectAvgProgressGroupByProjectRequirementIds(requirementIds, excludedStatusCodes); - if (rows != null) { - for (Map row : rows) { - Object idObj = row.get("projectRequirementId"); - Object progressObj = row.get("progressRate"); - if (idObj == null || progressObj == null) { - continue; - } - Long reqId = ((Number) idObj).longValue(); - BigDecimal avg = progressObj instanceof BigDecimal - ? (BigDecimal) progressObj - : new BigDecimal(progressObj.toString()); - ownAvgMap.put(reqId, avg); - } - } - - // 按 parentId 建子需求索引(顶级父 id = 0) - Map> childrenIndex = allRequirements.stream() - .collect(Collectors.groupingBy(r -> r.getParentId() == null ? 0L : r.getParentId())); - - // DFS 自下而上递归 - Map result = new HashMap<>(); - for (ProjectRequirementDO r : allRequirements) { - if (!result.containsKey(r.getId())) { - computeProgressDfs(r, childrenIndex, ownAvgMap, result); - } - } - return result; - } - - private BigDecimal computeProgressDfs(ProjectRequirementDO r, - Map> childrenIndex, - Map ownAvgMap, - Map result) { - if (result.containsKey(r.getId())) { - return result.get(r.getId()); - } - List pool = new ArrayList<>(); - BigDecimal ownAvg = ownAvgMap.get(r.getId()); - if (ownAvg != null) { - pool.add(ownAvg); - } - List children = childrenIndex.getOrDefault(r.getId(), List.of()); - for (ProjectRequirementDO child : children) { - pool.add(computeProgressDfs(child, childrenIndex, ownAvgMap, result)); - } - BigDecimal value; - if (pool.isEmpty()) { - value = normalizeProgress(BigDecimal.ZERO); - } else { - BigDecimal sum = pool.stream().reduce(BigDecimal.ZERO, BigDecimal::add); - value = sum.divide(BigDecimal.valueOf(pool.size()), 2, RoundingMode.HALF_UP); - } - result.put(r.getId(), value); - return value; - } - - /** - * 从 rdms_object_status_model 字典动态读取执行的"进度排除状态"列表, - * 当前命中 cancelled;任何时候运维通过 progress_excluded_flag 增减,service 层无需重新部署。 - */ - private List loadProgressExcludedExecutionStatusCodes() { - List codes = statusModelMapper - .selectProgressExcludedStatusCodesByObjectTypeEnabled(ProjectExecutionConstants.OBJECT_TYPE); - return codes == null ? Collections.emptyList() : codes; - } - - private BigDecimal normalizeProgress(BigDecimal value) { - if (value == null) { - return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); - } - return value.setScale(2, RoundingMode.HALF_UP); - } - private List buildModuleTree(List modules, Long parentId) { return modules.stream() .filter(module -> Objects.equals(module.getParentId(), parentId)) diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/review/RequirementReviewServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/review/RequirementReviewServiceImpl.java index 99f313f..6848d20 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/review/RequirementReviewServiceImpl.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/review/RequirementReviewServiceImpl.java @@ -79,7 +79,7 @@ public class RequirementReviewServiceImpl implements RequirementReviewService { @Override @CheckObjectPermission(objectType = ProductObjectConstants.OBJECT_TYPE, objectId = "#productId", - permission = "project:product:query") + permission = ProductObjectConstants.PERMISSION_QUERY) public RequirementReviewRespVO getProductRequirementReview(Long productId, Long requirementId) { ProductRequirementDO requirement = productRequirementMapper.selectById(requirementId); if (requirement == null || !Objects.equals(requirement.getProductId(), productId)) { @@ -117,7 +117,7 @@ public class RequirementReviewServiceImpl implements RequirementReviewService { @Override @CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE, objectId = "#projectId", - permission = "project:project:query") + permission = ProjectObjectConstants.PERMISSION_QUERY) public RequirementReviewRespVO getProjectRequirementReview(Long projectId, Long requirementId) { ProjectRequirementDO requirement = projectRequirementMapper.selectById(requirementId); if (requirement == null || !Objects.equals(requirement.getProjectId(), projectId)) { diff --git a/rdms-project/rdms-project-boot/src/test/java/com/njcn/rdms/module/project/service/project/ProjectRequirementServiceImplTest.java b/rdms-project/rdms-project-boot/src/test/java/com/njcn/rdms/module/project/service/project/ProjectRequirementServiceImplTest.java index 518c042..89c983c 100644 --- a/rdms-project/rdms-project-boot/src/test/java/com/njcn/rdms/module/project/service/project/ProjectRequirementServiceImplTest.java +++ b/rdms-project/rdms-project-boot/src/test/java/com/njcn/rdms/module/project/service/project/ProjectRequirementServiceImplTest.java @@ -129,130 +129,4 @@ class ProjectRequirementServiceImplTest extends BaseMockitoUnitTest { return requirement; } - // ============== TD-016 进度聚合 ============== - - /** - * TD-016:叶子需求无承接执行 → 进度 0.00。 - */ - @Test - void computeRequirementProgress_whenLeafHasNoExecution_shouldBeZero() { - Long projectId = 2001L; - ProjectRequirementDO leaf = buildRequirementWithParent(9001L, projectId, "implementing", 0L); - when(requirementMapper.selectList(any(com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX.class))) - .thenReturn(List.of(leaf)); - when(statusModelMapper.selectProgressExcludedStatusCodesByObjectTypeEnabled(ProjectExecutionConstants.OBJECT_TYPE)) - .thenReturn(List.of("cancelled")); - when(projectExecutionMapper.selectAvgProgressGroupByProjectRequirementIds(any(), any())) - .thenReturn(List.of()); - - Map result = projectRequirementService.computeRequirementProgressMapByProjectId(projectId); - - assertEquals(new BigDecimal("0.00"), result.get(9001L)); - } - - /** - * TD-016:叶子需求有 N 个执行,AVG(progress_rate) 即结果。 - * (SQL 层已经 GROUP BY 平均;service 只是把那个平均值放回 pool 平均,pool 大小 1 等于自身) - */ - @Test - void computeRequirementProgress_whenLeafHasExecutions_shouldUseAvg() { - Long projectId = 2001L; - ProjectRequirementDO leaf = buildRequirementWithParent(9001L, projectId, "implementing", 0L); - when(requirementMapper.selectList(any(com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX.class))) - .thenReturn(List.of(leaf)); - when(statusModelMapper.selectProgressExcludedStatusCodesByObjectTypeEnabled(ProjectExecutionConstants.OBJECT_TYPE)) - .thenReturn(List.of("cancelled")); - when(projectExecutionMapper.selectAvgProgressGroupByProjectRequirementIds(any(), any())) - .thenReturn(List.of(rowOf(9001L, "0.60"))); - - Map result = projectRequirementService.computeRequirementProgressMapByProjectId(projectId); - - assertEquals(new BigDecimal("0.60"), result.get(9001L)); - } - - /** - * TD-016:父需求 = AVG(自己挂的执行的平均, 每个直接子需求进度)。 - * 例:父 9000 自己挂的执行平均 0.40;子 9001(叶子)执行平均 0.80; - * 父 = AVG(0.40, 0.80) = 0.60。子 9001 = 0.80。 - */ - @Test - void computeRequirementProgress_whenParentHasOwnExecutionsAndChildren_shouldAveragePool() { - Long projectId = 2001L; - ProjectRequirementDO parent = buildRequirementWithParent(9000L, projectId, "implementing", 0L); - ProjectRequirementDO child = buildRequirementWithParent(9001L, projectId, "implementing", 9000L); - when(requirementMapper.selectList(any(com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX.class))) - .thenReturn(List.of(parent, child)); - when(statusModelMapper.selectProgressExcludedStatusCodesByObjectTypeEnabled(ProjectExecutionConstants.OBJECT_TYPE)) - .thenReturn(List.of("cancelled")); - when(projectExecutionMapper.selectAvgProgressGroupByProjectRequirementIds(any(), any())) - .thenReturn(List.of(rowOf(9000L, "0.40"), rowOf(9001L, "0.80"))); - - Map result = projectRequirementService.computeRequirementProgressMapByProjectId(projectId); - - assertEquals(new BigDecimal("0.80"), result.get(9001L)); - assertEquals(new BigDecimal("0.60"), result.get(9000L)); - } - - /** - * TD-016:父需求无自挂执行、有 2 个子需求 → 父 = AVG(子1, 子2)。 - */ - @Test - void computeRequirementProgress_whenParentHasNoOwnExecutionPureChildren_shouldAverageChildren() { - Long projectId = 2001L; - ProjectRequirementDO parent = buildRequirementWithParent(9000L, projectId, "implementing", 0L); - ProjectRequirementDO child1 = buildRequirementWithParent(9001L, projectId, "implementing", 9000L); - ProjectRequirementDO child2 = buildRequirementWithParent(9002L, projectId, "implementing", 9000L); - when(requirementMapper.selectList(any(com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX.class))) - .thenReturn(List.of(parent, child1, child2)); - when(statusModelMapper.selectProgressExcludedStatusCodesByObjectTypeEnabled(ProjectExecutionConstants.OBJECT_TYPE)) - .thenReturn(List.of("cancelled")); - when(projectExecutionMapper.selectAvgProgressGroupByProjectRequirementIds(any(), any())) - .thenReturn(List.of(rowOf(9001L, "0.50"), rowOf(9002L, "1.00"))); - - Map result = projectRequirementService.computeRequirementProgressMapByProjectId(projectId); - - assertEquals(new BigDecimal("0.50"), result.get(9001L)); - assertEquals(new BigDecimal("1.00"), result.get(9002L)); - assertEquals(new BigDecimal("0.75"), result.get(9000L)); - } - - /** - * TD-016:三层结构。爷父(9000) → 父(9100) → 叶子(9101),每层都挂执行; - * 验证自下而上递归正确。 - * 叶子 9101 自挂执行 0.80 → 9101 = 0.80 - * 父 9100 自挂执行 0.40 + 子 0.80 → AVG = 0.60 - * 爷 9000 自挂执行 0.20 + 子 0.60 → AVG = 0.40 - */ - @Test - void computeRequirementProgress_multiLevel_shouldRecurseFromBottomUp() { - Long projectId = 2001L; - ProjectRequirementDO grand = buildRequirementWithParent(9000L, projectId, "implementing", 0L); - ProjectRequirementDO parent = buildRequirementWithParent(9100L, projectId, "implementing", 9000L); - ProjectRequirementDO leaf = buildRequirementWithParent(9101L, projectId, "implementing", 9100L); - when(requirementMapper.selectList(any(com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX.class))) - .thenReturn(List.of(grand, parent, leaf)); - when(statusModelMapper.selectProgressExcludedStatusCodesByObjectTypeEnabled(ProjectExecutionConstants.OBJECT_TYPE)) - .thenReturn(List.of("cancelled")); - when(projectExecutionMapper.selectAvgProgressGroupByProjectRequirementIds(any(), any())) - .thenReturn(List.of(rowOf(9000L, "0.20"), rowOf(9100L, "0.40"), rowOf(9101L, "0.80"))); - - Map result = projectRequirementService.computeRequirementProgressMapByProjectId(projectId); - - assertEquals(new BigDecimal("0.80"), result.get(9101L)); - assertEquals(new BigDecimal("0.60"), result.get(9100L)); - assertEquals(new BigDecimal("0.40"), result.get(9000L)); - } - - private ProjectRequirementDO buildRequirementWithParent(Long id, Long projectId, String statusCode, Long parentId) { - ProjectRequirementDO requirement = buildRequirement(id, projectId, statusCode); - requirement.setParentId(parentId); - return requirement; - } - - private Map rowOf(Long requirementId, String avg) { - Map row = new HashMap<>(); - row.put("projectRequirementId", requirementId); - row.put("progressRate", new BigDecimal(avg)); - return row; - } }