From cd56e2e987db211488dbbf11a8e7e6e430a9cca9 Mon Sep 17 00:00:00 2001 From: JiaoTianBo Date: Sat, 28 Mar 2026 18:53:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(project):=20=E5=AE=9E=E7=8E=B0AI=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E5=88=9D=E5=A7=8B=E5=8C=96=E5=8F=8A=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增ProjectService接口及实现,实现基于AI解析项目文档生成结构化项目数据 - 使用聊天机器人接口和OSS服务支持文件内容读取与AI解析 - 实现项目基本信息、里程碑、任务、成员、资源、风险及时间节点的持久化保存 - 支持任务层级关系和依赖关系的存储与管理 - 提供项目列表、项目详情、项目甘特图及项目统计接口 - 项目列表支持管理员与普通用户视角不同的数据访问权限 - 统计功能覆盖项目状态分布、本月新增、即将到期、平均进度及高风险项目数 - 项目编号自动生成,状态初始化为规划中 - 采用分页查询支持大数据量高效访问 --- .../project/ProjectQueryController.java | 25 + .../yinlihupo/domain/vo/ProjectDetailVO.java | 645 ++++++++++++++++++ .../service/project/ProjectService.java | 9 + .../project/impl/ProjectServiceImpl.java | 286 ++++++++ 4 files changed, 965 insertions(+) create mode 100644 src/main/java/cn/yinlihupo/domain/vo/ProjectDetailVO.java diff --git a/src/main/java/cn/yinlihupo/controller/project/ProjectQueryController.java b/src/main/java/cn/yinlihupo/controller/project/ProjectQueryController.java index 8839430..14b5ffb 100644 --- a/src/main/java/cn/yinlihupo/controller/project/ProjectQueryController.java +++ b/src/main/java/cn/yinlihupo/controller/project/ProjectQueryController.java @@ -4,6 +4,7 @@ import cn.yinlihupo.common.core.BaseResponse; import cn.yinlihupo.common.page.TableDataInfo; import cn.yinlihupo.common.util.ResultUtils; import cn.yinlihupo.common.util.SecurityUtils; +import cn.yinlihupo.domain.vo.ProjectDetailVO; import cn.yinlihupo.domain.vo.ProjectGanttVO; import cn.yinlihupo.domain.vo.ProjectListVO; import cn.yinlihupo.domain.vo.ProjectStatisticsVO; @@ -66,6 +67,30 @@ public class ProjectQueryController { } } + /** + * 获取项目详情(全量信息) + * 包括基本信息、参与人员、里程碑、任务、资源、风险、时间节点等 + * + * @param projectId 项目ID + * @return 项目详情 + */ + @GetMapping("/{projectId}") + public BaseResponse getProjectDetail(@PathVariable Long projectId) { + log.info("获取项目详情, projectId: {}", projectId); + + if (projectId == null || projectId <= 0) { + return ResultUtils.error("项目ID不能为空"); + } + + try { + ProjectDetailVO result = projectService.getProjectDetail(projectId); + return ResultUtils.success("查询成功", result); + } catch (Exception e) { + log.error("获取项目详情失败: {}", e.getMessage(), e); + return ResultUtils.error("查询失败: " + e.getMessage()); + } + } + /** * 获取项目甘特图数据 * diff --git a/src/main/java/cn/yinlihupo/domain/vo/ProjectDetailVO.java b/src/main/java/cn/yinlihupo/domain/vo/ProjectDetailVO.java new file mode 100644 index 0000000..8a3f1ce --- /dev/null +++ b/src/main/java/cn/yinlihupo/domain/vo/ProjectDetailVO.java @@ -0,0 +1,645 @@ +package cn.yinlihupo.domain.vo; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 项目详情VO + * 包含项目全量信息:基本信息、参与人员、预算进度、物料资源等 + */ +@Data +public class ProjectDetailVO { + + // ==================== 项目基本信息 ==================== + + /** + * 项目ID + */ + private Long id; + + /** + * 项目编号 + */ + private String projectCode; + + /** + * 项目名称 + */ + private String projectName; + + /** + * 项目类型 + */ + private String projectType; + + /** + * 项目描述 + */ + private String description; + + /** + * 项目目标 + */ + private String objectives; + + /** + * 项目经理ID + */ + private Long managerId; + + /** + * 项目经理名称 + */ + private String managerName; + + /** + * 项目发起人ID + */ + private Long sponsorId; + + /** + * 计划开始日期 + */ + private LocalDate planStartDate; + + /** + * 计划结束日期 + */ + private LocalDate planEndDate; + + /** + * 实际开始日期 + */ + private LocalDate actualStartDate; + + /** + * 实际结束日期 + */ + private LocalDate actualEndDate; + + /** + * 项目预算 + */ + private BigDecimal budget; + + /** + * 已花费金额 + */ + private BigDecimal cost; + + /** + * 币种 + */ + private String currency; + + /** + * 进度百分比 + */ + private Integer progress; + + /** + * 状态 + */ + private String status; + + /** + * 优先级 + */ + private String priority; + + /** + * 风险等级 + */ + private String riskLevel; + + /** + * 可见性 + */ + private Integer visibility; + + /** + * 标签列表 + */ + private List tags; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + // ==================== 统计汇总信息 ==================== + + /** + * 成员数量 + */ + private Integer memberCount; + + /** + * 任务总数 + */ + private Integer taskCount; + + /** + * 已完成任务数 + */ + private Integer completedTaskCount; + + /** + * 里程碑总数 + */ + private Integer milestoneCount; + + /** + * 资源总数 + */ + private Integer resourceCount; + + /** + * 风险总数 + */ + private Integer riskCount; + + /** + * 高风险数量 + */ + private Integer highRiskCount; + + // ==================== 关联数据列表 ==================== + + /** + * 项目成员列表 + */ + private List members; + + /** + * 里程碑列表 + */ + private List milestones; + + /** + * 任务列表 + */ + private List tasks; + + /** + * 资源列表 + */ + private List resources; + + /** + * 风险列表 + */ + private List risks; + + /** + * 时间节点列表 + */ + private List timelineNodes; + + // ==================== 内部类定义 ==================== + + /** + * 成员信息 + */ + @Data + public static class MemberInfo { + /** + * 成员记录ID + */ + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户名 + */ + private String userName; + + /** + * 真实姓名 + */ + private String realName; + + /** + * 头像 + */ + private String avatar; + + /** + * 项目角色 + */ + private String roleCode; + + /** + * 部门 + */ + private String department; + + /** + * 职责描述 + */ + private String responsibility; + + /** + * 每周投入小时数 + */ + private BigDecimal weeklyHours; + + /** + * 加入日期 + */ + private LocalDate joinDate; + + /** + * 状态 + */ + private Integer status; + } + + /** + * 里程碑信息 + */ + @Data + public static class MilestoneInfo { + /** + * 里程碑ID + */ + private Long id; + + /** + * 里程碑名称 + */ + private String milestoneName; + + /** + * 描述 + */ + private String description; + + /** + * 计划日期 + */ + private LocalDate planDate; + + /** + * 实际日期 + */ + private LocalDate actualDate; + + /** + * 状态 + */ + private String status; + + /** + * 完成进度 + */ + private Integer progress; + + /** + * 是否关键里程碑 + */ + private Integer isKey; + + /** + * 交付物列表 + */ + private List deliverables; + + /** + * 排序 + */ + private Integer sortOrder; + } + + /** + * 任务信息 + */ + @Data + public static class TaskInfo { + /** + * 任务ID + */ + private Long id; + + /** + * 任务编号 + */ + private String taskCode; + + /** + * 任务名称 + */ + private String taskName; + + /** + * 任务描述 + */ + private String description; + + /** + * 任务类型 + */ + private String taskType; + + /** + * 所属里程碑ID + */ + private Long milestoneId; + + /** + * 执行人ID + */ + private Long assigneeId; + + /** + * 执行人姓名 + */ + private String assigneeName; + + /** + * 计划开始日期 + */ + private LocalDate planStartDate; + + /** + * 计划结束日期 + */ + private LocalDate planEndDate; + + /** + * 实际开始日期 + */ + private LocalDate actualStartDate; + + /** + * 实际结束日期 + */ + private LocalDate actualEndDate; + + /** + * 计划工时 + */ + private BigDecimal planHours; + + /** + * 实际工时 + */ + private BigDecimal actualHours; + + /** + * 进度百分比 + */ + private Integer progress; + + /** + * 优先级 + */ + private String priority; + + /** + * 状态 + */ + private String status; + + /** + * 排序 + */ + private Integer sortOrder; + } + + /** + * 资源信息 + */ + @Data + public static class ResourceInfo { + /** + * 资源ID + */ + private Long id; + + /** + * 资源编号 + */ + private String resourceCode; + + /** + * 资源类型 + */ + private String resourceType; + + /** + * 资源名称 + */ + private String resourceName; + + /** + * 资源描述 + */ + private String description; + + /** + * 规格型号 + */ + private String specification; + + /** + * 单位 + */ + private String unit; + + /** + * 计划数量 + */ + private BigDecimal planQuantity; + + /** + * 实际数量 + */ + private BigDecimal actualQuantity; + + /** + * 单价 + */ + private BigDecimal unitPrice; + + /** + * 币种 + */ + private String currency; + + /** + * 供应商 + */ + private String supplier; + + /** + * 状态 + */ + private String status; + + /** + * 计划到位日期 + */ + private LocalDate planArriveDate; + + /** + * 实际到位日期 + */ + private LocalDate actualArriveDate; + + /** + * 负责人ID + */ + private Long responsibleId; + + /** + * 存放位置 + */ + private String location; + + /** + * 标签 + */ + private List tags; + } + + /** + * 风险信息 + */ + @Data + public static class RiskInfo { + /** + * 风险ID + */ + private Long id; + + /** + * 风险编号 + */ + private String riskCode; + + /** + * 风险分类 + */ + private String category; + + /** + * 风险名称 + */ + private String riskName; + + /** + * 风险描述 + */ + private String description; + + /** + * 发生概率 + */ + private BigDecimal probability; + + /** + * 影响程度 + */ + private BigDecimal impact; + + /** + * 风险得分 + */ + private BigDecimal riskScore; + + /** + * 风险等级 + */ + private String riskLevel; + + /** + * 状态 + */ + private String status; + + /** + * 负责人ID + */ + private Long ownerId; + + /** + * 缓解措施 + */ + private String mitigationPlan; + + /** + * 预期解决日期 + */ + private LocalDate dueDate; + + /** + * 发现时间 + */ + private LocalDateTime discoverTime; + } + + /** + * 时间节点信息 + */ + @Data + public static class TimelineInfo { + /** + * 节点ID + */ + private Long id; + + /** + * 节点名称 + */ + private String nodeName; + + /** + * 节点类型 + */ + private String nodeType; + + /** + * 计划日期 + */ + private LocalDate planDate; + + /** + * 实际日期 + */ + private LocalDate actualDate; + + /** + * 描述 + */ + private String description; + + /** + * 状态 + */ + private String status; + + /** + * 排序 + */ + private Integer sortOrder; + + /** + * 知识库范围 + */ + private List kbScope; + } +} diff --git a/src/main/java/cn/yinlihupo/service/project/ProjectService.java b/src/main/java/cn/yinlihupo/service/project/ProjectService.java index 35db2be..78e808c 100644 --- a/src/main/java/cn/yinlihupo/service/project/ProjectService.java +++ b/src/main/java/cn/yinlihupo/service/project/ProjectService.java @@ -1,6 +1,7 @@ package cn.yinlihupo.service.project; import cn.yinlihupo.common.page.TableDataInfo; +import cn.yinlihupo.domain.vo.ProjectDetailVO; import cn.yinlihupo.domain.vo.ProjectGanttVO; import cn.yinlihupo.domain.vo.ProjectInitResult; import cn.yinlihupo.domain.vo.ProjectListVO; @@ -90,4 +91,12 @@ public interface ProjectService { */ TableDataInfo getAllProjectList(Integer pageNum, Integer pageSize, String keyword, String status); + + /** + * 获取项目详情(全量信息) + * + * @param projectId 项目ID + * @return 项目详情 + */ + ProjectDetailVO getProjectDetail(Long projectId); } diff --git a/src/main/java/cn/yinlihupo/service/project/impl/ProjectServiceImpl.java b/src/main/java/cn/yinlihupo/service/project/impl/ProjectServiceImpl.java index ca22cc1..d71812b 100644 --- a/src/main/java/cn/yinlihupo/service/project/impl/ProjectServiceImpl.java +++ b/src/main/java/cn/yinlihupo/service/project/impl/ProjectServiceImpl.java @@ -45,6 +45,7 @@ public class ProjectServiceImpl implements ProjectService { private final ResourceMapper resourceMapper; private final RiskMapper riskMapper; private final ProjectTimelineMapper projectTimelineMapper; + private final SysUserMapper sysUserMapper; /** * 项目初始化系统提示词模板 @@ -825,4 +826,289 @@ public class ProjectServiceImpl implements ProjectService { vo.setSortOrder(task.getSortOrder()); return vo; } + + // ==================== 项目详情相关实现 ==================== + + @Override + public ProjectDetailVO getProjectDetail(Long projectId) { + log.info("查询项目详情, projectId: {}", projectId); + + // 1. 查询项目基本信息 + Project project = projectMapper.selectById(projectId); + if (project == null || project.getDeleted() == 1) { + throw new RuntimeException("项目不存在"); + } + + ProjectDetailVO detailVO = new ProjectDetailVO(); + + // 2. 设置项目基本信息 + detailVO.setId(project.getId()); + detailVO.setProjectCode(project.getProjectCode()); + detailVO.setProjectName(project.getProjectName()); + detailVO.setProjectType(project.getProjectType()); + detailVO.setDescription(project.getDescription()); + detailVO.setObjectives(project.getObjectives()); + detailVO.setManagerId(project.getManagerId()); + detailVO.setSponsorId(project.getSponsorId()); + detailVO.setPlanStartDate(project.getPlanStartDate()); + detailVO.setPlanEndDate(project.getPlanEndDate()); + detailVO.setActualStartDate(project.getActualStartDate()); + detailVO.setActualEndDate(project.getActualEndDate()); + detailVO.setBudget(project.getBudget()); + detailVO.setCost(project.getCost()); + detailVO.setCurrency(project.getCurrency()); + detailVO.setProgress(project.getProgress()); + detailVO.setStatus(project.getStatus()); + detailVO.setPriority(project.getPriority()); + detailVO.setRiskLevel(project.getRiskLevel()); + detailVO.setVisibility(project.getVisibility()); + detailVO.setTags(project.getTags()); + detailVO.setCreateTime(project.getCreateTime()); + detailVO.setUpdateTime(project.getUpdateTime()); + + // 查询项目经理名称 + if (project.getManagerId() != null) { + SysUser manager = sysUserMapper.selectById(project.getManagerId()); + if (manager != null) { + detailVO.setManagerName(manager.getRealName()); + } + } + + // 3. 查询项目成员 + List members = projectMemberMapper.selectList( + new LambdaQueryWrapper() + .eq(ProjectMember::getProjectId, projectId) + .eq(ProjectMember::getDeleted, 0) + .orderByAsc(ProjectMember::getId) + ); + + // 查询成员用户信息 + List userIds = members.stream() + .map(ProjectMember::getUserId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + Map userMap = new HashMap<>(); + if (!userIds.isEmpty()) { + List users = sysUserMapper.selectBatchIds(userIds); + userMap = users.stream() + .collect(Collectors.toMap(SysUser::getId, u -> u, (a, b) -> a)); + } + + // 转换成员信息 + final Map finalUserMap = userMap; + List memberVOList = members.stream() + .map(member -> { + ProjectDetailVO.MemberInfo memberInfo = new ProjectDetailVO.MemberInfo(); + memberInfo.setId(member.getId()); + memberInfo.setUserId(member.getUserId()); + memberInfo.setRoleCode(member.getRoleCode()); + memberInfo.setResponsibility(member.getResponsibility()); + memberInfo.setWeeklyHours(member.getWeeklyHours()); + memberInfo.setJoinDate(member.getJoinDate()); + memberInfo.setStatus(member.getStatus()); + + // 填充用户信息 + if (member.getUserId() != null) { + SysUser user = finalUserMap.get(member.getUserId()); + if (user != null) { + memberInfo.setUserName(user.getUsername()); + memberInfo.setRealName(user.getRealName()); + memberInfo.setAvatar(user.getAvatar()); + } + } + return memberInfo; + }) + .collect(Collectors.toList()); + detailVO.setMembers(memberVOList); + detailVO.setMemberCount(memberVOList.size()); + + // 4. 查询里程碑 + List milestones = projectMilestoneMapper.selectList( + new LambdaQueryWrapper() + .eq(ProjectMilestone::getProjectId, projectId) + .eq(ProjectMilestone::getDeleted, 0) + .orderByAsc(ProjectMilestone::getSortOrder) + ); + + List milestoneVOList = milestones.stream() + .map(milestone -> { + ProjectDetailVO.MilestoneInfo milestoneInfo = new ProjectDetailVO.MilestoneInfo(); + milestoneInfo.setId(milestone.getId()); + milestoneInfo.setMilestoneName(milestone.getMilestoneName()); + milestoneInfo.setDescription(milestone.getDescription()); + milestoneInfo.setPlanDate(milestone.getPlanDate()); + milestoneInfo.setActualDate(milestone.getActualDate()); + milestoneInfo.setStatus(milestone.getStatus()); + milestoneInfo.setProgress(milestone.getProgress()); + milestoneInfo.setIsKey(milestone.getIsKey()); + milestoneInfo.setDeliverables(milestone.getDeliverables()); + milestoneInfo.setSortOrder(milestone.getSortOrder()); + return milestoneInfo; + }) + .collect(Collectors.toList()); + detailVO.setMilestones(milestoneVOList); + detailVO.setMilestoneCount(milestoneVOList.size()); + + // 5. 查询任务 + List tasks = taskMapper.selectList( + new LambdaQueryWrapper() + .eq(Task::getProjectId, projectId) + .eq(Task::getDeleted, 0) + .orderByAsc(Task::getSortOrder) + ); + + // 查询任务执行人信息 + List assigneeIds = tasks.stream() + .map(Task::getAssigneeId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + Map assigneeMap = new HashMap<>(); + if (!assigneeIds.isEmpty()) { + List assignees = sysUserMapper.selectBatchIds(assigneeIds); + assigneeMap = assignees.stream() + .collect(Collectors.toMap(SysUser::getId, u -> u, (a, b) -> a)); + } + + // 转换任务信息 + final Map finalAssigneeMap = assigneeMap; + List taskVOList = tasks.stream() + .map(task -> { + ProjectDetailVO.TaskInfo taskInfo = new ProjectDetailVO.TaskInfo(); + taskInfo.setId(task.getId()); + taskInfo.setTaskCode(task.getTaskCode()); + taskInfo.setTaskName(task.getTaskName()); + taskInfo.setDescription(task.getDescription()); + taskInfo.setTaskType(task.getTaskType()); + taskInfo.setMilestoneId(task.getMilestoneId()); + taskInfo.setAssigneeId(task.getAssigneeId()); + taskInfo.setPlanStartDate(task.getPlanStartDate()); + taskInfo.setPlanEndDate(task.getPlanEndDate()); + taskInfo.setActualStartDate(task.getActualStartDate()); + taskInfo.setActualEndDate(task.getActualEndDate()); + taskInfo.setPlanHours(task.getPlanHours()); + taskInfo.setActualHours(task.getActualHours()); + taskInfo.setProgress(task.getProgress()); + taskInfo.setPriority(task.getPriority()); + taskInfo.setStatus(task.getStatus()); + taskInfo.setSortOrder(task.getSortOrder()); + + // 填充执行人姓名 + if (task.getAssigneeId() != null) { + SysUser assignee = finalAssigneeMap.get(task.getAssigneeId()); + if (assignee != null) { + taskInfo.setAssigneeName(assignee.getRealName()); + } + } + return taskInfo; + }) + .collect(Collectors.toList()); + detailVO.setTasks(taskVOList); + detailVO.setTaskCount(taskVOList.size()); + + // 统计已完成任务数 + int completedCount = (int) tasks.stream() + .filter(t -> "completed".equals(t.getStatus())) + .count(); + detailVO.setCompletedTaskCount(completedCount); + + // 6. 查询资源 + List resources = resourceMapper.selectList( + new LambdaQueryWrapper() + .eq(Resource::getProjectId, projectId) + .eq(Resource::getDeleted, 0) + .orderByAsc(Resource::getId) + ); + + List resourceVOList = resources.stream() + .map(resource -> { + ProjectDetailVO.ResourceInfo resourceInfo = new ProjectDetailVO.ResourceInfo(); + resourceInfo.setId(resource.getId()); + resourceInfo.setResourceCode(resource.getResourceCode()); + resourceInfo.setResourceType(resource.getResourceType()); + resourceInfo.setResourceName(resource.getResourceName()); + resourceInfo.setDescription(resource.getDescription()); + resourceInfo.setSpecification(resource.getSpecification()); + resourceInfo.setUnit(resource.getUnit()); + resourceInfo.setPlanQuantity(resource.getPlanQuantity()); + resourceInfo.setActualQuantity(resource.getActualQuantity()); + resourceInfo.setUnitPrice(resource.getUnitPrice()); + resourceInfo.setCurrency(resource.getCurrency()); + resourceInfo.setSupplier(resource.getSupplier()); + resourceInfo.setStatus(resource.getStatus()); + resourceInfo.setPlanArriveDate(resource.getPlanArriveDate()); + resourceInfo.setActualArriveDate(resource.getActualArriveDate()); + resourceInfo.setResponsibleId(resource.getResponsibleId()); + resourceInfo.setLocation(resource.getLocation()); + resourceInfo.setTags(resource.getTags()); + return resourceInfo; + }) + .collect(Collectors.toList()); + detailVO.setResources(resourceVOList); + detailVO.setResourceCount(resourceVOList.size()); + + // 7. 查询风险 + List risks = riskMapper.selectList( + new LambdaQueryWrapper() + .eq(Risk::getProjectId, projectId) + .eq(Risk::getDeleted, 0) + .orderByDesc(Risk::getRiskScore) + ); + + List riskVOList = risks.stream() + .map(risk -> { + ProjectDetailVO.RiskInfo riskInfo = new ProjectDetailVO.RiskInfo(); + riskInfo.setId(risk.getId()); + riskInfo.setRiskCode(risk.getRiskCode()); + riskInfo.setCategory(risk.getCategory()); + riskInfo.setRiskName(risk.getRiskName()); + riskInfo.setDescription(risk.getDescription()); + riskInfo.setProbability(risk.getProbability()); + riskInfo.setImpact(risk.getImpact()); + riskInfo.setRiskScore(risk.getRiskScore()); + riskInfo.setRiskLevel(risk.getRiskLevel()); + riskInfo.setStatus(risk.getStatus()); + riskInfo.setOwnerId(risk.getOwnerId()); + riskInfo.setMitigationPlan(risk.getMitigationPlan()); + riskInfo.setDueDate(risk.getDueDate()); + riskInfo.setDiscoverTime(risk.getDiscoverTime()); + return riskInfo; + }) + .collect(Collectors.toList()); + detailVO.setRisks(riskVOList); + detailVO.setRiskCount(riskVOList.size()); + + // 统计高风险数量 + int highRiskCount = (int) risks.stream() + .filter(r -> "high".equals(r.getRiskLevel()) || "critical".equals(r.getRiskLevel())) + .count(); + detailVO.setHighRiskCount(highRiskCount); + + // 8. 查询时间节点 + List timelines = projectTimelineMapper.selectList( + new LambdaQueryWrapper() + .eq(ProjectTimeline::getProjectId, projectId) + .eq(ProjectTimeline::getDeleted, 0) + .orderByAsc(ProjectTimeline::getSortOrder) + ); + + List timelineVOList = timelines.stream() + .map(timeline -> { + ProjectDetailVO.TimelineInfo timelineInfo = new ProjectDetailVO.TimelineInfo(); + timelineInfo.setId(timeline.getId()); + timelineInfo.setNodeName(timeline.getNodeName()); + timelineInfo.setNodeType(timeline.getNodeType()); + timelineInfo.setPlanDate(timeline.getPlanDate()); + timelineInfo.setActualDate(timeline.getActualDate()); + timelineInfo.setDescription(timeline.getDescription()); + timelineInfo.setStatus(timeline.getStatus()); + timelineInfo.setSortOrder(timeline.getSortOrder()); + timelineInfo.setKbScope(timeline.getKbScope()); + return timelineInfo; + }) + .collect(Collectors.toList()); + detailVO.setTimelineNodes(timelineVOList); + + return detailVO; + } }