diff --git a/xinnengyuan/ruoyi-admin/src/main/resources/application-prod.yml b/xinnengyuan/ruoyi-admin/src/main/resources/application-prod.yml
index 41f67e9e..fba67693 100644
--- a/xinnengyuan/ruoyi-admin/src/main/resources/application-prod.yml
+++ b/xinnengyuan/ruoyi-admin/src/main/resources/application-prod.yml
@@ -326,7 +326,7 @@ ys7:
app-key: 3acf9f1a43dc4209841e0893003db0a2
app-secret: 4bbf3e9394f55d3af6e3af27b2d3db36
job:
- capture-enabled: true # 控制是否启用萤石抓拍任务
+ capture-enabled: false # 控制是否启用萤石抓拍任务
# 斯巴达算法
sparta:
url: http://119.3.204.120:8040
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/mobileAttendanceMachine/DeviceMessageSender.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/mobileAttendanceMachine/DeviceMessageSender.java
index a96ecf02..acff4231 100644
--- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/mobileAttendanceMachine/DeviceMessageSender.java
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/mobileAttendanceMachine/DeviceMessageSender.java
@@ -1,15 +1,13 @@
package org.dromara.mobileAttendanceMachine;
import lombok.extern.log4j.Log4j2;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
/**
* @Author 铁憨憨
* @Date 2025/10/14 15:48
* @Version 1.0
- *
+ *
* 考勤设备消息发送工具类(对应Golang的ws包工具方法)
*/
@@ -18,10 +16,11 @@ import org.springframework.stereotype.Service;
public class DeviceMessageSender {
/**
* 组装下发人员信息(对应Golang的TheSenderInformationOfTheAssemblyPersonnel)
- * @param sn 设备编号
+ *
+ * @param sn 设备编号
* @param userId 用户ID
- * @param name 用户名
- * @param face 人脸模板地址(HTTP链接)
+ * @param name 用户名
+ * @param face 人脸模板地址(HTTP链接)
* @return 异常信息(无异常则返回null)
*/
public static Exception sendPersonnelInformation(String sn, String userId, String name, String face) {
@@ -55,6 +54,7 @@ public class DeviceMessageSender {
/**
* 获取打卡设备所有人员(对应Golang的SelectUserAll)
+ *
* @param sn 设备编号
* @return 响应结果(包含人员信息)
* @throws Exception 发送或接收过程中的异常
@@ -81,7 +81,8 @@ public class DeviceMessageSender {
/**
* 删除指定人员(对应Golang的DelByUserId)
- * @param sn 设备编号
+ *
+ * @param sn 设备编号
* @param userId 要删除的用户ID
* @return 响应结果
* @throws Exception 发送或接收过程中的异常
@@ -109,7 +110,8 @@ public class DeviceMessageSender {
/**
* 批量删除指定人员(对应Golang的BatchDelete)
- * @param sn 设备编号
+ *
+ * @param sn 设备编号
* @param userIds 要删除的用户ID列表
* @return 响应结果
* @throws Exception 发送或接收过程中的异常
@@ -137,6 +139,7 @@ public class DeviceMessageSender {
/**
* 删除指定考勤机全部人员(对应Golang的DelAll)
+ *
* @param sn 设备编号
* @return 响应结果
* @throws Exception 发送或接收过程中的异常
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/mobileAttendanceMachine/DeviceWebSocketServer.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/mobileAttendanceMachine/DeviceWebSocketServer.java
index fb2c00ea..33532311 100644
--- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/mobileAttendanceMachine/DeviceWebSocketServer.java
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/mobileAttendanceMachine/DeviceWebSocketServer.java
@@ -2,8 +2,6 @@ package org.dromara.mobileAttendanceMachine;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j2;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/mobileAttendanceMachine/KqjEntity.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/mobileAttendanceMachine/KqjEntity.java
index eaa3bf6d..e505dd7a 100644
--- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/mobileAttendanceMachine/KqjEntity.java
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/mobileAttendanceMachine/KqjEntity.java
@@ -3,7 +3,6 @@ package org.dromara.mobileAttendanceMachine;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
-import lombok.NoArgsConstructor;
import java.net.http.WebSocket;
@@ -15,7 +14,6 @@ import java.net.http.WebSocket;
@Data
@AllArgsConstructor
-@NoArgsConstructor
public class KqjEntity {
@Data
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/controller/PgsProgressCategoryController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/controller/PgsProgressCategoryController.java
index 2e811382..1f78cb59 100644
--- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/controller/PgsProgressCategoryController.java
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/controller/PgsProgressCategoryController.java
@@ -90,6 +90,16 @@ public class PgsProgressCategoryController extends BaseController {
return R.ok(list);
}
+ /**
+ * 查询进度类别的甘特图结构列表
+ */
+ @SaCheckPermission("progress:progressCategory:query")
+ @GetMapping("/list/gantt/{progressCategoryId}")
+ public R> listGanttStructure(@NotNull(message = "类别主键不能为空")
+ @PathVariable Long progressCategoryId) {
+ return R.ok(pgsProgressCategoryService.getGanttStructureList(progressCategoryId));
+ }
+
/**
* 导出进度类别列表
*/
@@ -353,7 +363,7 @@ public class PgsProgressCategoryController extends BaseController {
@SaCheckPermission("progress:progressCategory:query")
@GetMapping("/gantt/{projectId}")
public R> listGantt(@NotNull(message = "主键不能为空")
- @PathVariable Long projectId) {
+ @PathVariable Long projectId) {
return R.ok(pgsProgressCategoryService.listGanttByProject(projectId));
}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/domain/dto/progresscategory/PgsProgressCategoryProgressDto.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/domain/dto/progresscategory/PgsProgressCategoryProgressDto.java
new file mode 100644
index 00000000..cbddb447
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/domain/dto/progresscategory/PgsProgressCategoryProgressDto.java
@@ -0,0 +1,37 @@
+package org.dromara.progress.domain.dto.progresscategory;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * @author lilemy
+ * @date 2025-10-13 17:50
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class PgsProgressCategoryProgressDto {
+
+ /**
+ * 已完成百分比
+ */
+ private BigDecimal finishProgress;
+
+ /**
+ * 已计划百分比
+ */
+ private BigDecimal planProgress;
+
+ /**
+ * 未计划百分比
+ */
+ private BigDecimal unPlanProgress;
+
+ /**
+ * 延期百分比
+ */
+ private BigDecimal delayProgress;
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/domain/vo/progresscategory/PgsProgressCategoryGanttMatrixVo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/domain/vo/progresscategory/PgsProgressCategoryGanttMatrixVo.java
new file mode 100644
index 00000000..1509f64b
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/domain/vo/progresscategory/PgsProgressCategoryGanttMatrixVo.java
@@ -0,0 +1,37 @@
+package org.dromara.progress.domain.vo.progresscategory;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author lilemy
+ * @date 2025-10-14 16:24
+ */
+@Data
+public class PgsProgressCategoryGanttMatrixVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 2999870755203852970L;
+
+ /**
+ * 主键id
+ */
+ private Long matrixId;
+
+ /**
+ * 名称
+ */
+ private String matrixName;
+
+ /**
+ * 关联类别id
+ */
+ private Long progressCategoryId;
+
+ /**
+ * 关联类别名称
+ */
+ private String progressCategoryName;
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/domain/vo/progresscategory/PgsProgressCategoryGanttSubProjectVo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/domain/vo/progresscategory/PgsProgressCategoryGanttSubProjectVo.java
new file mode 100644
index 00000000..e10c26b8
--- /dev/null
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/domain/vo/progresscategory/PgsProgressCategoryGanttSubProjectVo.java
@@ -0,0 +1,51 @@
+package org.dromara.progress.domain.vo.progresscategory;
+
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author lilemy
+ * @date 2025-10-14 16:22
+ */
+@Data
+public class PgsProgressCategoryGanttSubProjectVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = -8517387026258889743L;
+
+ /**
+ * 关联结构(1子项目 2方阵 3项目)
+ */
+ private String relevancyStructure;
+
+ /**
+ * 主键id
+ */
+ private Long subProjectId;
+
+ /**
+ * 名称
+ */
+ @Translation(type = TransConstant.PROJECT_ID_TO_NAME,mapper = "subProjectId")
+ private String subProjectName;
+
+ /**
+ * 关联类别id
+ */
+ private Long progressCategoryId;
+
+ /**
+ * 关联类别名称
+ */
+ private String progressCategoryName;
+
+ /**
+ * 关联方阵结构列表
+ */
+ private List children;
+}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/domain/vo/progresscategory/PgsProgressCategoryGanttVo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/domain/vo/progresscategory/PgsProgressCategoryGanttVo.java
index c4a0d7b1..bab051d6 100644
--- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/domain/vo/progresscategory/PgsProgressCategoryGanttVo.java
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/domain/vo/progresscategory/PgsProgressCategoryGanttVo.java
@@ -43,7 +43,37 @@ public class PgsProgressCategoryGanttVo implements Serializable {
private LocalDate endDate;
/**
- * 进度
+ * 已完成百分比
*/
- private BigDecimal progress;
+ private BigDecimal finishProgress;
+
+ /**
+ * 已计划百分比
+ */
+ private BigDecimal planProgress;
+
+ /**
+ * 未计划百分比
+ */
+ private BigDecimal unPlanProgress;
+
+ /**
+ * 延期百分比
+ */
+ private BigDecimal delayProgress;
+
+ /**
+ * 关联结构(1子项目 2方阵 3项目)
+ */
+ private String relevancyStructure;
+
+ /**
+ * 完成状态(0未开始 1进行中 2已完成)
+ */
+ private String status;
+
+ /**
+ * 排序
+ */
+ private Long sort;
}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/IPgsProgressCategoryService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/IPgsProgressCategoryService.java
index 0ca22b2a..6d446563 100644
--- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/IPgsProgressCategoryService.java
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/IPgsProgressCategoryService.java
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.facility.domain.FacMatrix;
import org.dromara.progress.domain.PgsProgressCategory;
+import org.dromara.progress.domain.PgsProgressPlan;
import org.dromara.progress.domain.dto.progresscategory.*;
import org.dromara.progress.domain.vo.progresscategory.*;
@@ -198,6 +199,14 @@ public interface IPgsProgressCategoryService extends IService categoryList);
+ /**
+ * 获取项目进度百分比
+ *
+ * @param categoryList 项目进度列表
+ * @return 项目进度百分比
+ */
+ PgsProgressCategoryProgressDto getProgressPercentage(List categoryList, List planList);
+
/**
* 获取项目进度类别未完成数量
*
@@ -229,4 +238,12 @@ public interface IPgsProgressCategoryService extends IService listGanttByProject(Long projectId);
+
+ /**
+ * 根据进度类别,获取进度类别甘特图结构
+ *
+ * @param progressCategoryId 进度类别id
+ * @return 进度类别甘特图结构
+ */
+ List getGanttStructureList(Long progressCategoryId);
}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/impl/PgsProgressCategoryServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/impl/PgsProgressCategoryServiceImpl.java
index 2945bf20..8c911902 100644
--- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/impl/PgsProgressCategoryServiceImpl.java
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/impl/PgsProgressCategoryServiceImpl.java
@@ -1417,7 +1417,7 @@ public class PgsProgressCategoryServiceImpl extends ServiceImpl allChildren = allCategory.stream()
.filter(item -> {
String ancestors = item.getAncestors();
- return ancestors != null && ancestors.contains("," + topId + ",");
+ return ancestors != null && (ancestors.contains("," + topId + ",") || ancestors.contains("," + topId));
})
.toList();
@@ -1512,6 +1512,84 @@ public class PgsProgressCategoryServiceImpl extends ServiceImpl categoryList,
+ List planList) {
+ PgsProgressCategoryProgressDto dto = new PgsProgressCategoryProgressDto();
+ // 如果没有数据,则返回0
+ if (CollUtil.isEmpty(categoryList)) {
+ return dto;
+ }
+ // 总完成数量
+ BigDecimal totalCompleted = BigDecimal.ZERO;
+ // 总计划数量
+ BigDecimal totalPlan = BigDecimal.ZERO;
+ // 总延期数量
+ BigDecimal totalDelay = BigDecimal.ZERO;
+ // 总数量
+ BigDecimal totalWork = BigDecimal.ZERO;
+ // 遍历所有项目进度,计算总完成数和总数
+ for (PgsProgressCategory category : categoryList) {
+ BigDecimal completed = category.getCompleted();
+ BigDecimal total = category.getTotal();
+ if (PgsProgressUnitTypeEnum.PERCENTAGE.getValue().equals(category.getUnitType())) {
+ completed = completed.divide(BigDecimal.valueOf(100L), 4, RoundingMode.HALF_UP).multiply(total);
+ }
+ totalCompleted = totalCompleted.add(completed);
+ totalWork = totalWork.add(total);
+ // 如果当前类型已完成,则直接返回
+ if (completed.compareTo(total) == 0) {
+ continue;
+ }
+ // 获取当前类别的计划数量
+ BigDecimal plan = planList.stream()
+ .filter(item -> item.getProgressCategoryId().equals(category.getId()))
+ // 1. 获取延期数量为0的计划
+ .filter(item -> item.getDelayNumber().compareTo(BigDecimal.ZERO) == 0)
+ // 2. 过滤掉已完成数量 >= 计划数量的计划
+ .filter(item -> item.getFinishedNumber().compareTo(item.getPlanNumber()) < 0)
+ // 3. 对每个计划取「计划数量 - 完成数量」
+ .map(item -> {
+ BigDecimal planNumber = item.getPlanNumber();
+ if (PgsProgressUnitTypeEnum.PERCENTAGE.getValue().equals(category.getUnitType())) {
+ planNumber = planNumber.divide(BigDecimal.valueOf(100L), 4, RoundingMode.HALF_UP).multiply(total);
+ }
+ return planNumber.subtract(item.getFinishedNumber());
+ })
+ // 4. 累加
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ totalPlan = totalPlan.add(plan);
+ // 获取当前类别的延期数量
+ BigDecimal delay = planList.stream()
+ .filter(item -> item.getProgressCategoryId().equals(category.getId()))
+ .filter(item -> item.getDelayNumber().compareTo(BigDecimal.ZERO) > 0)
+ .map(item -> {
+ BigDecimal delayNum = item.getDelayNumber();
+ if (PgsProgressUnitTypeEnum.PERCENTAGE.getValue().equals(category.getUnitType())) {
+ delayNum = delayNum.divide(BigDecimal.valueOf(100L), 4, RoundingMode.HALF_UP).multiply(total);
+ }
+ return delayNum;
+ })
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ totalDelay = totalDelay.add(delay);
+ }
+ dto.setFinishProgress(BigDecimalUtil.toPercentage(totalCompleted, totalWork));
+ dto.setPlanProgress(BigDecimalUtil.toPercentage(totalPlan, totalWork));
+ // 计算延期量 -> 历史延迟量 - 未完成计划数量
+ totalDelay = totalDelay.subtract(totalPlan).max(BigDecimal.ZERO);
+ dto.setDelayProgress(BigDecimalUtil.toPercentage(totalDelay, totalWork));
+ // 计算未计划量
+ BigDecimal totalUnPlan = totalWork.subtract(totalCompleted).subtract(totalPlan).subtract(totalDelay);
+ dto.setUnPlanProgress(BigDecimalUtil.toPercentage(totalUnPlan, totalWork));
+ return dto;
+ }
+
/**
* 获取项目进度类别未完成数量
*
@@ -1886,43 +1964,318 @@ public class PgsProgressCategoryServiceImpl extends ServiceImpl progressCategoryList = this.lambdaQuery()
.in(PgsProgressCategory::getProjectId, projectIds)
- .eq(PgsProgressCategory::getMatrixId, 0)
.list();
if (CollUtil.isEmpty(progressCategoryList)) {
return ganttList;
}
- // 封装进度类别数据
- List list = progressCategoryList.stream().map(p -> {
- // 获取所有子节点
- List children = this.getLeafNodesByTopId(p.getId(), progressCategoryList);
- PgsProgressCategoryGanttVo vo = new PgsProgressCategoryGanttVo();
- vo.setId(p.getId());
- vo.setParentId(p.getParentId());
- vo.setText(p.getName());
- vo.setStartDate(null);
- vo.setEndDate(null);
- vo.setProgress(this.getCompletedPercentage(children)
- .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
- return vo;
- }).toList();
- ganttList.addAll(list);
// 获取当前项目所有计划
List planList = progressPlanService.lambdaQuery()
- .eq(PgsProgressPlan::getProjectId, projectId)
+ .in(PgsProgressPlan::getProjectId, projectIds)
.list();
- List list1 = planList.stream().map(p -> {
- PgsProgressCategoryGanttVo vo = new PgsProgressCategoryGanttVo();
- vo.setId(p.getId());
- vo.setParentId(p.getProgressCategoryId());
- vo.setText(p.getProgressCategoryName() + "-" + "计划");
- vo.setStartDate(p.getStartDate());
- vo.setEndDate(p.getEndDate());
- vo.setProgress(BigDecimalUtil.toPercentage(p.getFinishedNumber(), p.getPlanNumber())
- .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
- return vo;
- }).toList();
- ganttList.addAll(list1);
+ // 获取关联项目的类别和计划
+ List projectCategoryList = progressCategoryList.stream()
+ .filter(p -> p.getRelevancyStructure().equals(PgsRelevancyStructureEnum.PROJECT.getValue()))
+ .toList();
+
+ if (CollUtil.isNotEmpty(projectCategoryList)) {
+ // 封装进度类别数据
+ List list = projectCategoryList.stream().map(p -> {
+ // 获取所有子节点
+ List children = this.getLeafNodesByTopId(p.getId(), progressCategoryList);
+ PgsProgressCategoryGanttVo vo = new PgsProgressCategoryGanttVo();
+ vo.setId(p.getId());
+ vo.setParentId(p.getParentId());
+ vo.setText(p.getName());
+ PgsProgressCategoryGanttVo ganttVo = this.getCategoryStartDateAndEndDate(p.getId(), progressCategoryList, planList);
+ vo.setStartDate(ganttVo.getStartDate());
+ vo.setEndDate(ganttVo.getEndDate());
+ if (CollUtil.isEmpty(children)) {
+ children = List.of(p);
+ }
+ // 获取当前子节点的所有计划
+ List finalChildren = children;
+ List childrenPlanList = planList.stream()
+ .filter(plan -> finalChildren.stream().anyMatch(category -> category.getId().equals(plan.getProgressCategoryId())))
+ .toList();
+ PgsProgressCategoryProgressDto percentage = this.getProgressPercentage(finalChildren, childrenPlanList);
+ vo.setFinishProgress(percentage.getFinishProgress());
+ vo.setPlanProgress(percentage.getPlanProgress());
+ vo.setUnPlanProgress(percentage.getUnPlanProgress());
+ vo.setDelayProgress(percentage.getDelayProgress());
+ vo.setRelevancyStructure(PgsRelevancyStructureEnum.PROJECT.getValue());
+ vo.setStatus(CollUtil.isNotEmpty(children) ? this.getParentStatus(children) : p.getStatus());
+ vo.setSort(p.getSort());
+ return vo;
+ }).toList();
+ ganttList.addAll(list);
+ }
+ // 获取关联子项目的类别和计划
+ List subProjectCategoryList = progressCategoryList.stream()
+ .filter(p -> p.getRelevancyStructure().equals(PgsRelevancyStructureEnum.SUB_PROJECT.getValue()))
+ .toList();
+ if (CollUtil.isNotEmpty(subProjectCategoryList)) {
+ // 获取顶层的类别
+ List topCategoryList = subProjectCategoryList.stream()
+ .filter(p -> p.getParentId().equals(0L))
+ .toList();
+ Map> topMap = topCategoryList.stream()
+ .collect(Collectors.groupingBy(PgsProgressCategory::getName));
+ // 封装进度类别数据
+ List list = new ArrayList<>();
+ for (Map.Entry> entry : topMap.entrySet()) {
+ List value = entry.getValue();
+ // 递归封装本身及子节点数据
+ this.getCategoryGanttAndChildrenVo(0L, value, list, progressCategoryList, planList);
+ }
+ ganttList.addAll(list);
+ }
+ // 获取关联方阵项目的类别和计划
+ List matrixCategoryList = progressCategoryList.stream()
+ .filter(p -> p.getRelevancyStructure().equals(PgsRelevancyStructureEnum.MATRIX.getValue()))
+ .toList();
+ if (CollUtil.isNotEmpty(matrixCategoryList)) {
+ // 获取顶层的类别
+ List topCategoryList = matrixCategoryList.stream()
+ .filter(p -> p.getParentId().equals(0L))
+ .toList();
+ Map> topMap = topCategoryList.stream()
+ .collect(Collectors.groupingBy(PgsProgressCategory::getName));
+ // 封装进度类别数据
+ List list = new ArrayList<>();
+ for (Map.Entry> entry : topMap.entrySet()) {
+ List value = entry.getValue();
+ // 递归封装本身及子节点数据
+ this.getCategoryGanttAndChildrenVo(0L, value, list, progressCategoryList, planList);
+ }
+ ganttList.addAll(list);
+ }
+ // 按 sort 字段升序排序
+ ganttList.sort(Comparator.comparing(PgsProgressCategoryGanttVo::getSort));
return ganttList;
}
+ /**
+ * 根据进度类别,获取进度类别甘特图结构
+ *
+ * @param progressCategoryId 进度类别id
+ * @return 进度类别甘特图结构
+ */
+ @Override
+ public List getGanttStructureList(Long progressCategoryId) {
+ // 获取类别信息
+ PgsProgressCategory category = this.getById(progressCategoryId);
+ if (category == null) {
+ throw new ServiceException("进度类别不存在", HttpStatus.NOT_FOUND);
+ }
+ // 获取项目信息
+ BusProject project = projectService.getById(category.getProjectId());
+ // 整合所有项目id
+ List projects = projectService.lambdaQuery()
+ .eq(BusProject::getPId, project.getPId())
+ .list();
+ List projectIds = projects.stream().map(BusProject::getId).toList();
+ String name = category.getName();
+ String relevancyStructure = category.getRelevancyStructure();
+ // 获取对应的进度类别列表
+ List progressCategoryList = this.lambdaQuery()
+ .in(PgsProgressCategory::getProjectId, projectIds)
+ .eq(PgsProgressCategory::getName, name)
+ .eq(PgsProgressCategory::getRelevancyStructure, relevancyStructure)
+ .list();
+ if (PgsRelevancyStructureEnum.SUB_PROJECT.getValue().equals(relevancyStructure)) {
+ // 构建关联子项目的结构
+ return progressCategoryList.stream().map(p -> {
+ PgsProgressCategoryGanttSubProjectVo vo = new PgsProgressCategoryGanttSubProjectVo();
+ vo.setRelevancyStructure(PgsRelevancyStructureEnum.SUB_PROJECT.getValue());
+ vo.setSubProjectId(p.getProjectId());
+ vo.setProgressCategoryId(p.getId());
+ vo.setProgressCategoryName(p.getName());
+ return vo;
+ }).toList();
+ } else if (PgsRelevancyStructureEnum.MATRIX.getValue().equals(relevancyStructure)) {
+ // 构建关联方阵的结构
+ Map> map = progressCategoryList.stream()
+ .collect(Collectors.groupingBy(PgsProgressCategory::getProjectId));
+ return map.entrySet().stream().map(entry -> {
+ Long key = entry.getKey();
+ List value = entry.getValue();
+ PgsProgressCategoryGanttSubProjectVo vo = new PgsProgressCategoryGanttSubProjectVo();
+ vo.setRelevancyStructure(PgsRelevancyStructureEnum.MATRIX.getValue());
+ vo.setSubProjectId(key);
+ vo.setProgressCategoryName(name);
+ // 封装关联方阵结构
+ List children = value.stream().map(v -> {
+ PgsProgressCategoryGanttMatrixVo matrixVo = new PgsProgressCategoryGanttMatrixVo();
+ matrixVo.setMatrixId(v.getMatrixId());
+ matrixVo.setMatrixName(v.getMatrixName());
+ matrixVo.setProgressCategoryId(v.getId());
+ matrixVo.setProgressCategoryName(v.getName());
+ return matrixVo;
+ }).toList();
+ vo.setChildren(children);
+ return vo;
+ }).toList();
+ } else {
+ return List.of();
+ }
+ }
+
+ /**
+ * 计算进度类别(含子类别)的开始和结束时间。
+ *
+ * 逻辑说明:
+ * 1. 若该进度类别为叶子节点,则直接根据其关联的计划表(Plan)的开始/结束时间计算。
+ * 2. 若该进度类别有子节点,则递归计算所有子节点的开始/结束时间,
+ * 以最早的开始时间和最晚的结束时间作为该类别的时间区间。
+ *
+ *
+ *
+ * @param progressCategoryId 当前进度类别ID
+ * @param allChildren 所有进度类别(树形结构的扁平化列表)
+ * @param allPlanList 所有进度计划列表
+ * @return 当前进度类别的开始和结束时间(可能为空)
+ */
+ private PgsProgressCategoryGanttVo getCategoryStartDateAndEndDate(Long progressCategoryId,
+ List allChildren,
+ List allPlanList) {
+ PgsProgressCategoryGanttVo ganttVo = new PgsProgressCategoryGanttVo();
+
+ // 获取该类别下的所有叶子节点(无子节点的类别)
+ List children = this.getLeafNodesByTopId(progressCategoryId, allChildren);
+
+ if (CollUtil.isEmpty(children)) {
+ // --- 叶子节点:直接根据计划计算 ---
+ List planList = allPlanList.stream()
+ .filter(p -> p.getProgressCategoryId().equals(progressCategoryId))
+ .toList();
+
+ if (CollUtil.isNotEmpty(planList)) {
+ LocalDate startDate = planList.stream()
+ .map(PgsProgressPlan::getStartDate)
+ .filter(Objects::nonNull)
+ .min(LocalDate::compareTo)
+ .orElse(null);
+
+ LocalDate endDate = planList.stream()
+ .map(PgsProgressPlan::getEndDate)
+ .filter(Objects::nonNull)
+ .max(LocalDate::compareTo)
+ .orElse(null);
+
+ ganttVo.setStartDate(startDate);
+ ganttVo.setEndDate(endDate);
+ }
+
+ } else {
+ // --- 非叶子节点:递归计算子节点 ---
+ List childVoList = children.stream()
+ .map(child -> getCategoryStartDateAndEndDate(child.getId(), allChildren, allPlanList))
+ .toList();
+
+ LocalDate minStart = childVoList.stream()
+ .map(PgsProgressCategoryGanttVo::getStartDate)
+ .filter(Objects::nonNull)
+ .min(LocalDate::compareTo)
+ .orElse(null);
+
+ LocalDate maxEnd = childVoList.stream()
+ .map(PgsProgressCategoryGanttVo::getEndDate)
+ .filter(Objects::nonNull)
+ .max(LocalDate::compareTo)
+ .orElse(null);
+
+ ganttVo.setStartDate(minStart);
+ ganttVo.setEndDate(maxEnd);
+ }
+ return ganttVo;
+ }
+
+ /**
+ * 根据名称,递归获取进度类别(含子类别)的甘特图数据。
+ *
+ * 逻辑说明:
+ * 1. 获取该进度类别下的所有叶子节点(无子节点的类别)。
+ * 2. 递归获取所有叶子节点的甘特图数据。
+ * 3. 获取所有叶子节点的甘特图数据,并添加到结果列表中。
+ *
+ *
+ * @param parentId 父级ID
+ * @param value 当前进度类别列表
+ * @param list 结果列表
+ * @param progressCategoryList 所有进度类别列表
+ * @param planList 所有进度计划列表
+ */
+ private void getCategoryGanttAndChildrenVo(Long parentId,
+ List value,
+ List list,
+ List progressCategoryList,
+ List planList) {
+ if (CollUtil.isEmpty(value)) {
+ return;
+ }
+ // 封装数据
+ PgsProgressCategoryGanttVo vo = new PgsProgressCategoryGanttVo();
+ PgsProgressCategory first = value.getFirst();
+ vo.setId(first.getId());
+ vo.setParentId(parentId);
+ vo.setText(first.getName());
+ // 获取子节点
+ List children = value.stream()
+ .map(category -> getLeafNodesByTopId(category.getId(), progressCategoryList))
+ .filter(CollUtil::isNotEmpty)
+ .flatMap(Collection::stream)
+ .distinct()
+ .toList();
+ if (CollUtil.isEmpty(children)) {
+ children = value;
+ }
+ // 获取子节点的进度计划
+ List finalChildren = children;
+ List childrenPlanList = planList.stream()
+ .filter(plan -> finalChildren.stream().anyMatch(category -> category.getId().equals(plan.getProgressCategoryId())))
+ .toList();
+ // 获取进度情况
+ PgsProgressCategoryProgressDto percentage = this.getProgressPercentage(finalChildren, childrenPlanList);
+ vo.setFinishProgress(percentage.getFinishProgress());
+ vo.setPlanProgress(percentage.getPlanProgress());
+ vo.setUnPlanProgress(percentage.getUnPlanProgress());
+ vo.setDelayProgress(percentage.getDelayProgress());
+ // 计算所有类别的开始时间和结束时间
+ List ganttVoList = value.stream()
+ .map(category -> getCategoryStartDateAndEndDate(category.getId(), progressCategoryList, planList))
+ .toList();
+ // 获取最小开始时间
+ LocalDate minStart = ganttVoList.stream()
+ .map(PgsProgressCategoryGanttVo::getStartDate)
+ .filter(Objects::nonNull)
+ .min(LocalDate::compareTo)
+ .orElse(null);
+ // 获取最大结束时间
+ LocalDate maxEnd = ganttVoList.stream()
+ .map(PgsProgressCategoryGanttVo::getEndDate)
+ .filter(Objects::nonNull)
+ .max(LocalDate::compareTo)
+ .orElse(null);
+ vo.setStartDate(minStart);
+ vo.setEndDate(maxEnd);
+ vo.setRelevancyStructure(first.getRelevancyStructure());
+ vo.setStatus(CollUtil.isNotEmpty(children) ? this.getParentStatus(children) : first.getStatus());
+ vo.setSort(first.getSort());
+ list.add(vo);
+ // 递归,获取子节点的数据
+ if (CollUtil.isNotEmpty(children)) {
+ int level = first.getAncestors().split(",").length + 1;
+ // 仅获取下一层级的子节点数据
+ List nextLevelChildren = children.stream()
+ .filter(child -> child.getAncestors().split(",").length == level)
+ .distinct()
+ .toList();
+ Map> nextMap = nextLevelChildren.stream()
+ .collect(Collectors.groupingBy(PgsProgressCategory::getName));
+ for (Map.Entry> entry : nextMap.entrySet()) {
+ getCategoryGanttAndChildrenVo(first.getId(), entry.getValue(), list, progressCategoryList, planList);
+ }
+ }
+ }
+
}
diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/impl/PgsProgressPlanServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/impl/PgsProgressPlanServiceImpl.java
index 075885c8..e1cce7b7 100644
--- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/impl/PgsProgressPlanServiceImpl.java
+++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/progress/service/impl/PgsProgressPlanServiceImpl.java
@@ -127,16 +127,6 @@ public class PgsProgressPlanServiceImpl extends ServiceImpl