fix(产品需求): 解决测试后存在的一些问题。

This commit is contained in:
dk
2026-05-09 13:44:38 +08:00
parent 7575784c01
commit 604bf61981
10 changed files with 365 additions and 182 deletions

View File

@@ -16,19 +16,19 @@ public class ProductRequirementRespVO {
@Schema(description = "需求ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "父需求ID0表示顶级需求", example = "0")
@Schema(description = "父需求ID0 表示顶级需求", example = "0")
private Long parentId;
@Schema(description = "所属模块ID", example = "1024")
private Long moduleId;
@Schema(description = "是否需要评审0不需要1需要", example = "0")
@Schema(description = "是否需要评审0不需要1需要", example = "0")
private Integer reviewRequired;
@Schema(description = "需求标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "支持需求模块化管理")
private String title;
@Schema(description = "需求描述富文本", example = "<p>详细描述需求内容</p>")
@Schema(description = "需求描述,支持富文本", example = "<p>详细描述需求内容</p>")
private String description;
@Schema(description = "需求分类字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "function")
@@ -37,13 +37,13 @@ public class ProductRequirementRespVO {
@Schema(description = "需求分类名称", example = "功能需求")
private String categoryName;
@Schema(description = "来源类型manual:手工新增, work_order:工单流转", requiredMode = Schema.RequiredMode.REQUIRED, example = "manual")
@Schema(description = "需求来源类型manual 表示手工新增work_order 表示工单流转", requiredMode = Schema.RequiredMode.REQUIRED, example = "manual")
private String sourceType;
@Schema(description = "来源业务ID", example = "1024")
private Long sourceBizId;
@Schema(description = "优先级0低 1中 2高 3紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Schema(description = "优先级0低1中2高3紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer priority;
@Schema(description = "优先级名称", example = "")
@@ -58,13 +58,16 @@ public class ProductRequirementRespVO {
@Schema(description = "最近一次状态动作原因", example = "评审通过")
private String lastStatusReason;
@Schema(description = "提出人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@Schema(description = "提出人用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long proposerId;
@Schema(description = "提出人用户姓名", example = "张三")
private String proposerNickname;
@Schema(description = "当前处理人用户编号", example = "1024")
@Schema(description = "所需工时", example = "8")
private Double workHours;
@Schema(description = "当前处理人用户ID", example = "1024")
private Long currentHandlerUserId;
@Schema(description = "当前处理人姓名", example = "李四")
@@ -76,9 +79,6 @@ public class ProductRequirementRespVO {
@Schema(description = "实现项目名称", example = "NPQS-10086")
private String implementProjectName;
@Schema(description = "预期完成时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime completionDate;
@Schema(description = "排序值", example = "0")
private Integer sort;
@@ -88,10 +88,10 @@ public class ProductRequirementRespVO {
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
@Schema(description = "子需求列表树形结构")
@Schema(description = "子需求列表树形结构")
private List<ProductRequirementRespVO> children;
@Schema(description = "是否为终态已拒绝、已取消、已关闭", example = "false")
@Schema(description = "是否为终态已拒绝、已取消、已关闭都算终态", example = "false")
private Boolean terminal;
}

View File

@@ -6,8 +6,6 @@ import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 管理后台 - 产品需求保存 Request VO
*/
@@ -22,45 +20,51 @@ public class ProductRequirementSaveReqVO {
@NotNull(message = "产品编号不能为空")
private Long productId;
@Schema(description = "所属模块ID为空时归入全部需求", example = "1024")
@Schema(description = "所属模块ID为空时归入全部需求模块", example = "1024")
private Long moduleId;
@Schema(description = "是否需要评审0不需要1需要", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@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个字符")
@Size(max = 200, message = "需求标题长度不能超过 200 个字符")
private String title;
@Schema(description = "需求描述富文本", example = "<p>详细描述需求内容</p>")
@Schema(description = "需求描述,支持富文本", example = "<p>详细描述需求内容</p>")
private String description;
@Schema(description = "需求分类字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "function")
@NotBlank(message = "需求分类不能为空")
@Size(max = 64, message = "需求分类长度不能超过64个字符")
@Size(max = 64, message = "需求分类长度不能超过 64 个字符")
private String category;
@Schema(description = "优先级0低 1中 2高 3紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Schema(description = "优先级0低1中2高3紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "优先级不能为空")
private Integer priority;
@Schema(description = "提出人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@Schema(description = "提出人用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "提出人不能为空")
private Long proposerId;
@Schema(description = "当前处理人用户编号", example = "1024")
@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 = "1024")
private Long implementProjectId;
@Schema(description = "预期完成时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "预期完成时间不能为空")
private LocalDateTime completionDate;
@Schema(description = "排序值(越小越靠前)", example = "0")
@Schema(description = "排序值,越小越靠前", example = "0")
private Integer sort;
}

View File

@@ -6,8 +6,6 @@ import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 管理后台 - 产品需求拆分 Request VO
*/
@@ -23,45 +21,51 @@ public class ProductRequirementSplitReqVO {
@NotNull(message = "产品编号不能为空")
private Long productId;
@Schema(description = "所属模块ID为空时归入全部需求", example = "1024")
@Schema(description = "所属模块ID为空时归入全部需求模块", example = "1024")
private Long moduleId;
@Schema(description = "是否需要评审0不需要1需要", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@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个字符")
@Size(max = 200, message = "需求标题长度不能超过 200 个字符")
private String title;
@Schema(description = "需求描述富文本", example = "<p>详细描述需求内容</p>")
@Schema(description = "需求描述,支持富文本", example = "<p>详细描述需求内容</p>")
private String description;
@Schema(description = "需求分类字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "function")
@NotBlank(message = "需求分类不能为空")
@Size(max = 64, message = "需求分类长度不能超过64个字符")
@Size(max = 64, message = "需求分类长度不能超过 64 个字符")
private String category;
@Schema(description = "优先级0低 1中 2高 3紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Schema(description = "优先级0低1中2高3紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "优先级不能为空")
private Integer priority;
@Schema(description = "提出人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@Schema(description = "提出人用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "提出人不能为空")
private Long proposerId;
@Schema(description = "当前处理人用户编号", example = "1024")
@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 = "1024")
private Long implementProjectId;
@Schema(description = "预期完成时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "预期完成时间不能为空")
private LocalDateTime completionDate;
@Schema(description = "排序值(越小越靠前)", example = "0")
@Schema(description = "排序值,越小越靠前", example = "0")
private Integer sort;
}

View File

@@ -6,8 +6,6 @@ import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 管理后台 - 产品需求编辑 Request VO
*/
@@ -23,45 +21,51 @@ public class ProductRequirementUpdateReqVO {
@NotNull(message = "产品ID不能为空")
private Long productId;
@Schema(description = "所属模块ID为空时归入全部需求", example = "1024")
@Schema(description = "所属模块ID为空时归入全部需求模块", example = "1024")
private Long moduleId;
@Schema(description = "是否需要评审0不需要1需要", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@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个字符")
@Size(max = 200, message = "需求标题长度不能超过 200 个字符")
private String title;
@Schema(description = "需求描述富文本", example = "<p>详细描述需求内容</p>")
@Schema(description = "需求描述,支持富文本", example = "<p>详细描述需求内容</p>")
private String description;
@Schema(description = "需求分类字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "function")
@NotBlank(message = "需求分类不能为空")
@Size(max = 64, message = "需求分类长度不能超过64个字符")
@Size(max = 64, message = "需求分类长度不能超过 64 个字符")
private String category;
@Schema(description = "优先级0低 1中 2高 3紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Schema(description = "优先级0低1中2高3紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "优先级不能为空")
private Integer priority;
@Schema(description = "提出人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@Schema(description = "提出人用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "提出人不能为空")
private Long proposerId;
@Schema(description = "当前处理人用户编号", example = "1024")
@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 = "1024")
private Long implementProjectId;
@Schema(description = "预期完成时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "预期完成时间不能为空")
private LocalDateTime completionDate;
@Schema(description = "排序值(越小越靠前)", example = "0")
@Schema(description = "排序值,越小越靠前", example = "0")
private Integer sort;
}

View File

@@ -6,8 +6,6 @@ import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 产品需求主表
*/
@@ -22,11 +20,11 @@ public class ProductRequirementDO extends BaseDO {
@TableId
private Long id;
/**
* 父需求ID0表示顶级需求
* 父需求ID0 表示顶级需求
*/
private Long parentId;
/**
* 所属模块ID0表示全部需求
* 所属模块ID,空表示全部需求
*/
private Long moduleId;
/**
@@ -34,7 +32,7 @@ public class ProductRequirementDO extends BaseDO {
*/
private Long productId;
/**
* 是否需要评审0不需要1需要
* 是否需要评审0不需要1需要
*/
private Integer reviewRequired;
/**
@@ -42,7 +40,7 @@ public class ProductRequirementDO extends BaseDO {
*/
private String title;
/**
* 需求描述富文本
* 需求描述,支持富文本
*/
private String description;
/**
@@ -50,7 +48,7 @@ public class ProductRequirementDO extends BaseDO {
*/
private String category;
/**
* 来源类型manual:手工新增, work_order:工单流转
* 来源类型manual 表示手工新增work_order 表示工单流转
*/
private String sourceType;
/**
@@ -58,7 +56,7 @@ public class ProductRequirementDO extends BaseDO {
*/
private Long sourceBizId;
/**
* 优先级0低 1中 2高 3紧急
* 优先级0低1中2高3紧急
*/
private Integer priority;
/**
@@ -74,9 +72,13 @@ public class ProductRequirementDO extends BaseDO {
*/
private Long proposerId;
/**
* 提出人用户姓名快照
* 提出人姓名快照
*/
private String proposerNickname;
/**
* 所需工时
*/
private Double workHours;
/**
* 当前处理人用户ID
*/
@@ -86,15 +88,11 @@ public class ProductRequirementDO extends BaseDO {
*/
private String currentHandlerUserNickname;
/**
* 默认实现项目ID分流后填写)
* 默认实现项目ID分流后可回
*/
private Long implementProjectId;
/**
* 预期完成时间
*/
private LocalDateTime completionDate;
/**
* 排序值(越小越靠前)
* 排序值,越小越靠前
*/
private Integer sort;

View File

@@ -2,6 +2,7 @@ package com.njcn.rdms.module.project.service.product;
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.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.*;
@@ -25,6 +26,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -56,6 +58,8 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
private static final List<String> CHILD_ALLOW_CLOSE_STATUSES = List.of(STATUS_CLOSED, STATUS_CANCELLED, STATUS_REJECTED, STATUS_ACCEPTED);
// 允许删除的状态集合(实施中之前的状态)
private static final List<String> ALLOW_DELETE_STATUSES = List.of(STATUS_PENDING_CONFIRM, STATUS_PENDING_REVIEW, STATUS_PENDING_DISPATCH);
// 父需求取消时,子需求允许的状态集合(仅已拒绝和已取消)
private static final List<String> CHILD_ALLOW_CANCEL_STATUSES = List.of(STATUS_REJECTED, STATUS_CANCELLED);
// 权限常量
private static final String PRODUCT_CREATE_PERMISSION = "project:product:create";
@@ -113,14 +117,17 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
? STATUS_PENDING_REVIEW : STATUS_PENDING_DISPATCH;
requirement.setStatusCode(initialStatus);
requirement.setProposerId(createReqVO.getProposerId());
requirement.setProposerNickname(normalizeNullableText(createReqVO.getProposerNickname()));
requirement.setWorkHours(createReqVO.getWorkHours());
requirement.setCurrentHandlerUserId(createReqVO.getCurrentHandlerUserId());
requirement.setCurrentHandlerUserNickname(normalizeNullableText(createReqVO.getCurrentHandlerUserNickname()));
requirement.setImplementProjectId(createReqVO.getImplementProjectId());
requirement.setCompletionDate(createReqVO.getCompletionDate());
requirement.setSort(createReqVO.getSort() != null ? createReqVO.getSort() : 0);
requirementMapper.insert(requirement);
// 写入业务审计日志
writeBizAuditLog(requirement, ACTION_CREATE, null, initialStatus, null, null);
writeBizAuditLog(requirement, ACTION_CREATE, null, initialStatus,
buildRequirementFieldChanges(null, requirement), null);
return requirement.getId();
}
@@ -137,6 +144,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
// 校验模块是否属于当前产品
validateModuleBelongsToProduct(moduleId, updateReqVO.getProductId());
ProductRequirementDO before = cloneRequirement(requirement);
String fromStatus = requirement.getStatusCode();
requirement.setModuleId(moduleId);
requirement.setReviewRequired(updateReqVO.getReviewRequired());
@@ -145,13 +153,16 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
requirement.setCategory(updateReqVO.getCategory());
requirement.setPriority(updateReqVO.getPriority());
requirement.setProposerId(updateReqVO.getProposerId());
requirement.setProposerNickname(normalizeNullableText(updateReqVO.getProposerNickname()));
requirement.setWorkHours(updateReqVO.getWorkHours());
requirement.setCurrentHandlerUserId(updateReqVO.getCurrentHandlerUserId());
requirement.setCurrentHandlerUserNickname(normalizeNullableText(updateReqVO.getCurrentHandlerUserNickname()));
requirement.setImplementProjectId(updateReqVO.getImplementProjectId());
requirement.setCompletionDate(updateReqVO.getCompletionDate());
requirement.setSort(updateReqVO.getSort() != null ? updateReqVO.getSort() : 0);
requirementMapper.updateById(requirement);
writeBizAuditLog(requirement, ACTION_UPDATE, fromStatus, requirement.getStatusCode(), null, null);
writeBizAuditLog(requirement, ACTION_UPDATE, fromStatus, requirement.getStatusCode(),
buildRequirementFieldChanges(before, requirement), null);
}
@Override
@@ -370,6 +381,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
String actionCode = reqVO.getActionCode().trim();
Long implementProjectId = reqVO.getImplementProjectId();
String fromStatus = requirement.getStatusCode();
ProductRequirementDO before = cloneRequirement(requirement);
// 校验状态流转是否合法
ObjectStatusTransitionDO transition = validateRequirementTransition(fromStatus, actionCode);
@@ -383,6 +395,10 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
if ("accept".equals(actionCode) || "close".equals(actionCode)) {
validateAllChildrenAllowCloseOrAccept(reqVO.getId());
}
// cancel动作时如果是父需求则校验所有子需求是否处于已拒绝或已取消
if ("cancel".equals(actionCode) && Objects.equals(requirement.getParentId(), 0L)) {
validateParentCancelAllowed(reqVO.getId());
}
// close动作时递归关闭所有已验收的子需求包括子子需求
if ("close".equals(actionCode)) {
closeAllAcceptedChildren(reqVO.getId(), reason);
@@ -394,11 +410,15 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
}
requirement.setStatusCode(toStatus);
requirement.setLastStatusReason(reason);
if (implementProjectId != null) {
requirement.setImplementProjectId(implementProjectId);
}
// 写入状态变更日志
writeRequirementStatusLog(requirement, actionCode, fromStatus, toStatus, reason);
// 写入业务审计日志
writeBizAuditLog(requirement, actionCode, fromStatus, toStatus, null, reason);
writeBizAuditLog(requirement, actionCode, fromStatus, toStatus,
buildRequirementFieldChanges(before, requirement), reason);
}
/**
@@ -417,6 +437,23 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
}
}
/**
* 校验父需求取消时,所有子需求(包括子子需求)是否处于允许取消的状态
* 只有子需求全部为已拒绝或已取消时,父需求才允许取消
*/
@VisibleForTesting
void validateParentCancelAllowed(Long requirementId) {
List<ProductRequirementDO> allChildren = getAllRequirementsWithChildren(requirementId);
// 排除自身,只校验子需求
for (ProductRequirementDO req : allChildren) {
if (!Objects.equals(req.getId(), requirementId)) {
if (!CHILD_ALLOW_CANCEL_STATUSES.contains(req.getStatusCode())) {
throw exception(ErrorCodeConstants.REQUIREMENT_CHILD_NOT_ALLOW_CANCEL);
}
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
@@ -442,7 +479,8 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_CONCURRENT_MODIFIED);
}
writeBizAuditLog(requirement, ACTION_DELETE, fromStatus, null, null, null);
writeBizAuditLog(requirement, ACTION_DELETE, fromStatus, null,
buildRequirementFieldChanges(requirement, null), null);
}
@Override
@@ -471,27 +509,32 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
String initialStatus = Objects.equals(reqVO.getReviewRequired(), 1)
? STATUS_PENDING_REVIEW : STATUS_PENDING_DISPATCH;
childRequirement.setStatusCode(initialStatus);
childRequirement.setProposerId(parentRequirement.getProposerId()); // 继承父需求提出人
childRequirement.setProposerId(reqVO.getProposerId());
childRequirement.setProposerNickname(normalizeNullableText(reqVO.getProposerNickname()));
childRequirement.setWorkHours(reqVO.getWorkHours());
childRequirement.setCurrentHandlerUserId(reqVO.getCurrentHandlerUserId());
childRequirement.setCurrentHandlerUserNickname(normalizeNullableText(reqVO.getCurrentHandlerUserNickname()));
childRequirement.setImplementProjectId(reqVO.getImplementProjectId());
childRequirement.setCompletionDate(reqVO.getCompletionDate());
childRequirement.setSort(reqVO.getSort() != null ? reqVO.getSort() : 0);
requirementMapper.insert(childRequirement);
// 父需求状态从待分流变为实施中
if (STATUS_PENDING_DISPATCH.equals(parentRequirement.getStatusCode())) {
ProductRequirementDO before = cloneRequirement(parentRequirement);
String fromStatus = parentRequirement.getStatusCode();
int updateCount = requirementMapper.updateStatusByIdAndStatus(
parentRequirement.getId(), fromStatus, STATUS_IMPLEMENTING, null);
if (updateCount == 1) {
parentRequirement.setStatusCode(STATUS_IMPLEMENTING);
writeRequirementStatusLog(parentRequirement, ACTION_SPLIT, fromStatus, STATUS_IMPLEMENTING, null);
writeBizAuditLog(parentRequirement, ACTION_SPLIT, fromStatus, STATUS_IMPLEMENTING, null, null);
writeBizAuditLog(parentRequirement, ACTION_SPLIT, fromStatus, STATUS_IMPLEMENTING,
buildRequirementFieldChanges(before, parentRequirement), null);
}
}
// 写入子需求的业务审计日志
writeBizAuditLog(childRequirement, ACTION_CREATE, null, initialStatus, null, null);
writeBizAuditLog(childRequirement, ACTION_CREATE, null, initialStatus,
buildRequirementFieldChanges(null, childRequirement), null);
return childRequirement.getId();
}
@@ -503,6 +546,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
ProductRequirementDO requirement = validateRequirementExists(reqVO.getId());
String fromStatus = requirement.getStatusCode();
String reason = reqVO.getReason().trim();
ProductRequirementDO before = cloneRequirement(requirement);
// 校验当前状态是否为已验收(只有已验收才能关闭)
if (!STATUS_ACCEPTED.equals(fromStatus)) {
@@ -525,7 +569,8 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
requirement.setLastStatusReason(reason);
writeRequirementStatusLog(requirement, ACTION_CLOSE, fromStatus, STATUS_CLOSED, reason);
writeBizAuditLog(requirement, ACTION_CLOSE, fromStatus, STATUS_CLOSED, null, reason);
writeBizAuditLog(requirement, ACTION_CLOSE, fromStatus, STATUS_CLOSED,
buildRequirementFieldChanges(before, requirement), reason);
}
/**
@@ -538,11 +583,15 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
closeAllAcceptedChildren(child.getId(), reason);
// 如果子需求已验收,则关闭
if (STATUS_ACCEPTED.equals(child.getStatusCode())) {
ProductRequirementDO before = cloneRequirement(child);
int updateCount = requirementMapper.updateStatusByIdAndStatus(
child.getId(), STATUS_ACCEPTED, STATUS_CLOSED, reason);
if (updateCount == 1) {
child.setStatusCode(STATUS_CLOSED);
child.setLastStatusReason(reason);
writeRequirementStatusLog(child, ACTION_CLOSE, STATUS_ACCEPTED, STATUS_CLOSED, reason);
writeBizAuditLog(child, ACTION_CLOSE, STATUS_ACCEPTED, STATUS_CLOSED, null, reason);
writeBizAuditLog(child, ACTION_CLOSE, STATUS_ACCEPTED, STATUS_CLOSED,
buildRequirementFieldChanges(before, child), reason);
}
}
}
@@ -894,6 +943,98 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
bizAuditLogMapper.insert(auditLog);
}
/**
* 克隆需求快照,避免更新前后的对象引用互相污染。
*/
private ProductRequirementDO cloneRequirement(ProductRequirementDO source) {
ProductRequirementDO target = new ProductRequirementDO();
target.setId(source.getId());
target.setParentId(source.getParentId());
target.setModuleId(source.getModuleId());
target.setProductId(source.getProductId());
target.setReviewRequired(source.getReviewRequired());
target.setTitle(source.getTitle());
target.setDescription(source.getDescription());
target.setCategory(source.getCategory());
target.setSourceType(source.getSourceType());
target.setSourceBizId(source.getSourceBizId());
target.setPriority(source.getPriority());
target.setStatusCode(source.getStatusCode());
target.setLastStatusReason(source.getLastStatusReason());
target.setProposerId(source.getProposerId());
target.setProposerNickname(source.getProposerNickname());
target.setWorkHours(source.getWorkHours());
target.setCurrentHandlerUserId(source.getCurrentHandlerUserId());
target.setCurrentHandlerUserNickname(source.getCurrentHandlerUserNickname());
target.setImplementProjectId(source.getImplementProjectId());
target.setSort(source.getSort());
return target;
}
/**
* 构建需求字段变更内容,供业务审计日志落库。
*/
private String buildRequirementFieldChanges(ProductRequirementDO before, ProductRequirementDO after) {
Map<String, Object> fieldChanges = new LinkedHashMap<>();
appendFieldChange(fieldChanges, "parentId", valueOf(before, ProductRequirementDO::getParentId),
valueOf(after, ProductRequirementDO::getParentId));
appendFieldChange(fieldChanges, "moduleId", valueOf(before, ProductRequirementDO::getModuleId),
valueOf(after, ProductRequirementDO::getModuleId));
appendFieldChange(fieldChanges, "productId", valueOf(before, ProductRequirementDO::getProductId),
valueOf(after, ProductRequirementDO::getProductId));
appendFieldChange(fieldChanges, "reviewRequired", valueOf(before, ProductRequirementDO::getReviewRequired),
valueOf(after, ProductRequirementDO::getReviewRequired));
appendFieldChange(fieldChanges, "title", valueOf(before, ProductRequirementDO::getTitle),
valueOf(after, ProductRequirementDO::getTitle));
appendFieldChange(fieldChanges, "description", valueOf(before, ProductRequirementDO::getDescription),
valueOf(after, ProductRequirementDO::getDescription));
appendFieldChange(fieldChanges, "category", valueOf(before, ProductRequirementDO::getCategory),
valueOf(after, ProductRequirementDO::getCategory));
appendFieldChange(fieldChanges, "sourceType", valueOf(before, ProductRequirementDO::getSourceType),
valueOf(after, ProductRequirementDO::getSourceType));
appendFieldChange(fieldChanges, "sourceBizId", valueOf(before, ProductRequirementDO::getSourceBizId),
valueOf(after, ProductRequirementDO::getSourceBizId));
appendFieldChange(fieldChanges, "priority", valueOf(before, ProductRequirementDO::getPriority),
valueOf(after, ProductRequirementDO::getPriority));
appendFieldChange(fieldChanges, "statusCode", valueOf(before, ProductRequirementDO::getStatusCode),
valueOf(after, ProductRequirementDO::getStatusCode));
appendFieldChange(fieldChanges, "lastStatusReason", valueOf(before, ProductRequirementDO::getLastStatusReason),
valueOf(after, ProductRequirementDO::getLastStatusReason));
appendFieldChange(fieldChanges, "proposerId", valueOf(before, ProductRequirementDO::getProposerId),
valueOf(after, ProductRequirementDO::getProposerId));
appendFieldChange(fieldChanges, "proposerNickname", valueOf(before, ProductRequirementDO::getProposerNickname),
valueOf(after, ProductRequirementDO::getProposerNickname));
appendFieldChange(fieldChanges, "workHours", valueOf(before, ProductRequirementDO::getWorkHours),
valueOf(after, ProductRequirementDO::getWorkHours));
appendFieldChange(fieldChanges, "currentHandlerUserId", valueOf(before, ProductRequirementDO::getCurrentHandlerUserId),
valueOf(after, ProductRequirementDO::getCurrentHandlerUserId));
appendFieldChange(fieldChanges, "currentHandlerUserNickname",
valueOf(before, ProductRequirementDO::getCurrentHandlerUserNickname),
valueOf(after, ProductRequirementDO::getCurrentHandlerUserNickname));
appendFieldChange(fieldChanges, "implementProjectId", valueOf(before, ProductRequirementDO::getImplementProjectId),
valueOf(after, ProductRequirementDO::getImplementProjectId));
appendFieldChange(fieldChanges, "sort", valueOf(before, ProductRequirementDO::getSort),
valueOf(after, ProductRequirementDO::getSort));
return fieldChanges.isEmpty() ? null : JsonUtils.toJsonString(fieldChanges);
}
private <T> T valueOf(ProductRequirementDO requirement, Function<ProductRequirementDO, T> getter) {
return requirement == null ? null : getter.apply(requirement);
}
/**
* 仅记录真实发生变化的字段,避免审计日志写入冗余内容。
*/
private void appendFieldChange(Map<String, Object> fieldChanges, String fieldName, Object before, Object after) {
if (Objects.equals(before, after)) {
return;
}
Map<String, Object> value = new LinkedHashMap<>();
value.put("before", before);
value.put("after", after);
fieldChanges.put(fieldName, value);
}
/**
* 处理可能为空的文本去除首尾空格后若为空则返回null
*/

View File

@@ -22,12 +22,13 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@@ -65,8 +66,9 @@ class ProductRequirementServiceImplTest extends BaseMockitoUnitTest {
reqVO.setReviewRequired(0); // 不需要评审
reqVO.setPriority(1);
reqVO.setProposerId(2001L);
reqVO.setProposerNickname("proposer-a");
reqVO.setCurrentHandlerUserId(2002L);
reqVO.setCompletionDate(LocalDateTime.now());
reqVO.setCurrentHandlerUserNickname("handler-a");
// 模拟"全部需求"模块存在parentId = 0L 的根模块)
ProductRequirementModuleDO defaultModule = createDefaultModule(defaultModuleId, 100L);
@@ -85,6 +87,12 @@ class ProductRequirementServiceImplTest extends BaseMockitoUnitTest {
assertEquals("测试需求", created.getTitle());
assertEquals(defaultModuleId, created.getModuleId()); // 未选择模块时自动归属到"全部需求"模块
assertEquals(100L, created.getProductId());
assertEquals("handler-a", created.getCurrentHandlerUserNickname());
ArgumentCaptor<BizAuditLogDO> auditCaptor = ArgumentCaptor.forClass(BizAuditLogDO.class);
verify(bizAuditLogMapper, times(1)).insert(auditCaptor.capture());
assertNotNull(auditCaptor.getValue().getFieldChanges());
assertTrue(auditCaptor.getValue().getFieldChanges().contains("currentHandlerUserNickname"));
}
@Test
@@ -98,7 +106,6 @@ class ProductRequirementServiceImplTest extends BaseMockitoUnitTest {
reqVO.setReviewRequired(1); // 需要评审
reqVO.setPriority(2);
reqVO.setProposerId(2001L);
reqVO.setCompletionDate(LocalDateTime.now());
// 模拟"全部需求"模块存在parentId = 0L 的根模块)
ProductRequirementModuleDO defaultModule = createDefaultModule(defaultModuleId, 100L);
@@ -130,7 +137,6 @@ class ProductRequirementServiceImplTest extends BaseMockitoUnitTest {
reqVO.setReviewRequired(0);
reqVO.setPriority(1);
reqVO.setProposerId(2001L);
reqVO.setCompletionDate(LocalDateTime.now());
when(requirementMapper.selectById(requirementId)).thenReturn(requirement);
@@ -155,7 +161,6 @@ class ProductRequirementServiceImplTest extends BaseMockitoUnitTest {
reqVO.setReviewRequired(0);
reqVO.setPriority(2);
reqVO.setProposerId(2001L);
reqVO.setCompletionDate(LocalDateTime.now());
// 模拟"全部需求"模块存在未选择模块时自动归属parentId = 0L 的根模块)
ProductRequirementModuleDO defaultModule = createDefaultModule(defaultModuleId, 100L);
@@ -303,7 +308,6 @@ class ProductRequirementServiceImplTest extends BaseMockitoUnitTest {
reqVO.setTitle("子需求");
reqVO.setCategory("function");
reqVO.setPriority(1);
reqVO.setCompletionDate(LocalDateTime.now());
when(requirementMapper.selectById(parentId)).thenReturn(parent);
when(requirementMapper.updateStatusByIdAndStatus(parentId, "pending_dispatch", "implementing", null))
@@ -320,7 +324,7 @@ class ProductRequirementServiceImplTest extends BaseMockitoUnitTest {
assertEquals(parentId, child.getParentId());
assertEquals("pending_dispatch", child.getStatusCode());
assertEquals(parent.getModuleId(), child.getModuleId());
assertEquals(parent.getProposerId(), child.getProposerId());
assertEquals(reqVO.getProposerId(), child.getProposerId());
// 验证父需求状态变为实施中
verify(requirementMapper, times(1))
@@ -342,7 +346,6 @@ class ProductRequirementServiceImplTest extends BaseMockitoUnitTest {
reqVO.setTitle("子需求2");
reqVO.setCategory("function");
reqVO.setPriority(1);
reqVO.setCompletionDate(LocalDateTime.now());
when(requirementMapper.selectById(parentId)).thenReturn(parent);
@@ -366,7 +369,6 @@ class ProductRequirementServiceImplTest extends BaseMockitoUnitTest {
reqVO.setTitle("子需求");
reqVO.setCategory("function");
reqVO.setPriority(1);
reqVO.setCompletionDate(LocalDateTime.now());
when(requirementMapper.selectById(parentId)).thenReturn(parent);

View File

@@ -4,11 +4,16 @@ import com.njcn.rdms.framework.apilog.core.annotation.ApiAccessLog;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.framework.excel.core.util.ExcelUtils;
import com.njcn.rdms.module.system.controller.admin.user.vo.user.UserSimpleRespVO;
import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationQueryReqVO;
import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationRespVO;
import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationSaveReqVO;
import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationTreeRespVO;
import com.njcn.rdms.module.system.convert.user.UserConvert;
import com.njcn.rdms.module.system.dal.dataobject.dept.DeptDO;
import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO;
import com.njcn.rdms.module.system.dal.dataobject.user.UserManagementRelationDO;
import com.njcn.rdms.module.system.service.dept.DeptService;
import com.njcn.rdms.module.system.service.user.UserManagementRelationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -22,9 +27,11 @@ import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static com.njcn.rdms.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
import static com.njcn.rdms.framework.common.util.collection.CollectionUtils.convertList;
/**
* 用户管理链路 Controller
@@ -43,6 +50,9 @@ import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@Validated
public class UserManagementRelationController {
@Resource
private DeptService deptService;
@Resource
private UserManagementRelationService userManagementRelationService;
@@ -147,6 +157,19 @@ public class UserManagementRelationController {
return success(list);
}
/**
* 获取未绑定直属上级的候选下级用户列表
* @return 候选下级用户列表
*/
@GetMapping("/candidate-users")
@Operation(summary = "获取未绑定直属上级的候选下级用户列表")
@PreAuthorize("@ss.hasPermission('system:user-management-relation:query')")
public CommonResult<List<UserSimpleRespVO>> getCandidateSubordinateUsers() {
List<AdminUserDO> users = userManagementRelationService.getCandidateSubordinateUsers();
Map<Long, DeptDO> deptMap = deptService.getDeptMap(convertList(users, AdminUserDO::getDeptId));
return success(UserConvert.INSTANCE.convertSimpleList(users, deptMap));
}
/**
* 获取用户管理链路树形结构
*

View File

@@ -5,6 +5,7 @@ import com.njcn.rdms.framework.common.util.collection.CollectionUtils;
import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationQueryReqVO;
import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationSaveReqVO;
import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationTreeRespVO;
import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO;
import com.njcn.rdms.module.system.dal.dataobject.user.UserManagementRelationDO;
import java.util.Collection;
@@ -99,6 +100,13 @@ public interface UserManagementRelationService {
*/
List<UserManagementRelationTreeRespVO> getRelationTree(UserManagementRelationQueryReqVO reqVO);
/**
* 获取还未绑定直属上级的候选下级用户列表
*
* @return 候选下级用户列表
*/
List<AdminUserDO> getCandidateSubordinateUsers();
/**
* 获得用户管理链路 Map
*

View File

@@ -3,6 +3,7 @@ package com.njcn.rdms.module.system.service.user;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.annotations.VisibleForTesting;
import com.njcn.rdms.framework.common.enums.CommonStatusEnum;
import com.njcn.rdms.framework.common.exception.ServiceException;
import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -243,7 +244,7 @@ public class UserManagementRelationServiceImpl implements UserManagementRelation
Long targetUserId = determineTargetUserId(reqVO, context);
if (targetUserId == null) {
return buildFullTree(context);
return Collections.emptyList();
}
return buildTargetBranchTree(targetUserId, context);
@@ -272,6 +273,32 @@ public class UserManagementRelationServiceImpl implements UserManagementRelation
return userManagementRelationMapper.selectListBySubordinateUserId(subordinateUserId);
}
/**
* 获取候选的下属用户列表
* @return 候选的下属用户列表
*/
@Override
public List<AdminUserDO> getCandidateSubordinateUsers() {
List<AdminUserDO> users = adminUserService.getUserListByStatus(CommonStatusEnum.ENABLE.getStatus());
if (CollUtil.isEmpty(users)) {
return Collections.emptyList();
}
List<UserManagementRelationDO> relations =
userManagementRelationMapper.selectList(new UserManagementRelationQueryReqVO());
Set<Long> boundSubordinateUserIds = relations.stream()
.map(UserManagementRelationDO::getSubordinateUserId)
.collect(Collectors.toSet());
return users.stream()
.filter(adminUserService::isUserAvailable)
.filter(user -> !boundSubordinateUserIds.contains(user.getId()))
.sorted(Comparator.comparing(AdminUserDO::getDeptId, Comparator.nullsLast(Long::compareTo))
.thenComparing(AdminUserDO::getNickname, Comparator.nullsLast(String::compareTo))
.thenComparing(AdminUserDO::getId))
.collect(Collectors.toList());
}
/**
* 获取用户管理链路树形结构
* 业务逻辑:
@@ -402,112 +429,50 @@ public class UserManagementRelationServiceImpl implements UserManagementRelation
}
/**
* 构建目标用户相关的分支树形结构
* 只包含目标用户的上级链和下级树,不包含同级节点
* 构建目标用户相关的分支树形结构(搜索模式)
* 只包含目标用户的上级链和目标用户本身,不显示下级
* <p>
* 构建逻辑:
* 1. 从目标用户向上追溯到根节点,记录上级链
* 2. 从根节点开始,只沿着包含目标用户的分支向下构建
* 3. 构建目标用户的所有下级
* 3. 目标用户节点不构建下级
*
* @param targetUserId 目标用户ID
* @param context 树形结构上下文
* @return 目标用户相关的分支树形结构列表
*/
private List<UserManagementRelationTreeRespVO> buildTargetBranchTree(Long targetUserId, TreeBuildContext context) {
LinkedList<Long> pathToRoot = findPathToRoot(targetUserId, context);
if (pathToRoot.isEmpty()) {
AdminUserDO targetUser = context.getUserMap().get(targetUserId);
if (targetUser == null) {
return Collections.emptyList();
}
Long rootUserId = pathToRoot.getFirst();
UserManagementRelationTreeRespVO rootNode = buildRootNode(rootUserId, context);
if (rootNode == null) {
UserManagementRelationDO targetRelation = context.getSubordinateToRelationMap().get(targetUserId);
Long targetRelationId = targetRelation != null ? targetRelation.getId() : null;
if (targetRelation == null || Objects.equals(targetRelation.getManagerUserId(), targetUserId)) {
// 根节点搜索时只返回本人,不继续展示更高层级
UserManagementRelationTreeRespVO targetNode =
buildTreeNodeForSearch(targetRelationId, targetUserId, targetUserId, context);
return targetNode == null ? Collections.emptyList() : Collections.singletonList(targetNode);
}
Long managerUserId = targetRelation.getManagerUserId();
UserManagementRelationDO managerRelation = context.getSubordinateToRelationMap().get(managerUserId);
Long managerRelationId = managerRelation != null ? managerRelation.getId() : null;
UserManagementRelationTreeRespVO managerNode =
buildTreeNodeForSearch(managerRelationId, managerUserId, managerUserId, context);
if (managerNode == null) {
return Collections.emptyList();
}
if (pathToRoot.size() > 1) {
buildBranchPath(rootNode, pathToRoot, context);
UserManagementRelationTreeRespVO targetNode =
buildTreeNodeForSearch(targetRelationId, targetUserId, managerUserId, context);
if (targetNode == null) {
return Collections.emptyList();
}
return Collections.singletonList(rootNode);
}
/**
* 从目标用户向上追溯到根节点,记录路径
*
* @param targetUserId 目标用户ID
* @param context 树形结构上下文
* @return 从根节点到目标用户的路径(包含根节点和目标用户)
*/
private LinkedList<Long> findPathToRoot(Long targetUserId, TreeBuildContext context) {
LinkedList<Long> path = new LinkedList<>();
Set<Long> visited = new HashSet<>();
Long currentUserId = targetUserId;
while (currentUserId != null && !visited.contains(currentUserId)) {
visited.add(currentUserId);
path.addFirst(currentUserId);
if (!context.getHasManagerUserIds().contains(currentUserId)) {
break;
}
UserManagementRelationDO relation = context.getSubordinateToRelationMap().get(currentUserId);
if (relation == null) {
break;
}
Long managerUserId = relation.getManagerUserId();
if (managerUserId.equals(currentUserId)) {
break;
}
currentUserId = managerUserId;
}
return path;
}
/**
* 沿着指定路径构建分支
* 从根节点的下一层开始,只构建路径中包含的用户节点
*
* @param parentNode 父节点
* @param pathToRoot 从根节点到目标用户的路径
* @param context 树形结构上下文
*/
private void buildBranchPath(UserManagementRelationTreeRespVO parentNode, LinkedList<Long> pathToRoot, TreeBuildContext context) {
if (pathToRoot.size() <= 1) {
return;
}
Long parentUserId = parentNode.getUserId();
List<Long> subordinateIds = context.getManagerToSubordinatesMap().get(parentUserId);
if (CollUtil.isEmpty(subordinateIds)) {
return;
}
Long nextUserIdInPath = pathToRoot.get(1);
for (Long subordinateId : subordinateIds) {
if (subordinateId.equals(parentUserId)) {
continue;
}
UserManagementRelationDO childRelation = context.getSubordinateToRelationMap().get(subordinateId);
Long childRelationId = childRelation != null ? childRelation.getId() : null;
if (subordinateId.equals(nextUserIdInPath)) {
UserManagementRelationTreeRespVO childNode = buildTreeNode(childRelationId, subordinateId, parentUserId, context);
if (childNode != null) {
parentNode.setChildren(Collections.singletonList(childNode));
if (pathToRoot.size() > 2) {
LinkedList<Long> remainingPath = new LinkedList<>(pathToRoot.subList(1, pathToRoot.size()));
buildBranchPath(childNode, remainingPath, context);
}
}
break;
}
}
managerNode.setChildren(Collections.singletonList(targetNode));
return Collections.singletonList(managerNode);
}
/**
@@ -611,6 +576,40 @@ public class UserManagementRelationServiceImpl implements UserManagementRelation
return node;
}
/**
* 构建树形节点(搜索模式)
* 与buildTreeNode的区别不构建下级节点只返回当前节点信息
*
* @param relationId 关系记录主键ID
* @param userId 当前用户ID
* @param managerUserId 上级用户ID
* @param context 树形结构上下文
* @return 树形节点(无下级)
*/
private UserManagementRelationTreeRespVO buildTreeNodeForSearch(Long relationId, Long userId, Long managerUserId,
TreeBuildContext context) {
AdminUserDO user = context.getUserMap().get(userId);
if (user == null) {
return null;
}
UserManagementRelationTreeRespVO node = new UserManagementRelationTreeRespVO();
node.setId(relationId);
node.setUserId(userId);
node.setUserNickname(user.getNickname());
node.setManagerUserId(managerUserId);
if (managerUserId != null) {
AdminUserDO managerUser = context.getUserMap().get(managerUserId);
if (managerUser != null) {
node.setManagerNickname(managerUser.getNickname());
}
}
node.setChildren(Collections.emptyList());
return node;
}
/**
* 统一时间有效性过滤条件:
* <p>