diff --git a/src/main/java/com/xujie/sys/modules/erf/service/impl/ErfApprovalCycleReportServiceImpl.java b/src/main/java/com/xujie/sys/modules/erf/service/impl/ErfApprovalCycleReportServiceImpl.java index e00ec697..3d9dcff4 100644 --- a/src/main/java/com/xujie/sys/modules/erf/service/impl/ErfApprovalCycleReportServiceImpl.java +++ b/src/main/java/com/xujie/sys/modules/erf/service/impl/ErfApprovalCycleReportServiceImpl.java @@ -2,13 +2,20 @@ package com.xujie.sys.modules.erf.service.impl; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.xujie.sys.common.utils.PageUtils; import com.xujie.sys.modules.erf.data.ErfApprovalCycleReportExportData; import com.xujie.sys.modules.erf.data.ErfApprovalCycleQueryData; import com.xujie.sys.modules.erf.dto.ErfApprovalCycleReportDto; +import com.xujie.sys.modules.erf.entity.ErfFlowInstance; +import com.xujie.sys.modules.erf.entity.ErfFlowNodeInstance; import com.xujie.sys.modules.erf.mapper.ErfExpApplyMapper; +import com.xujie.sys.modules.erf.mapper.ErfFlowInstanceMapper; +import com.xujie.sys.modules.erf.mapper.ErfFlowNodeInstanceMapper; import com.xujie.sys.modules.erf.service.ErfApprovalCycleReportService; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -35,11 +42,14 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; /** @@ -60,12 +70,17 @@ import java.util.stream.Collectors; @Service public class ErfApprovalCycleReportServiceImpl implements ErfApprovalCycleReportService { + private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); private static final int HEADER_ROW_COUNT = 2; private static final int COL_APPROVAL_CYCLE_DAYS = 13; private static final int COL_APPROVE_STATUS = 14; @Autowired private ErfExpApplyMapper erfExpApplyMapper; + @Autowired + private ErfFlowNodeInstanceMapper erfFlowNodeInstanceMapper; + @Autowired + private ErfFlowInstanceMapper erfFlowInstanceMapper; @Override public PageUtils queryPage(ErfApprovalCycleQueryData data) { @@ -76,6 +91,9 @@ public class ErfApprovalCycleReportServiceImpl implements ErfApprovalCycleReport List reportList = erfExpApplyMapper.getApprovalCycleReportList(data); PageInfo pageInfo = new PageInfo<>(reportList); + // 重新按当前审批批次计算各角色审批人和审批时间 + enrichApprovalFields(reportList); + // 计算审批周期及完成状态 reportList.forEach(this::calculateApprovalCycle); reportList = filterByAllApproveDate(reportList, data); @@ -90,6 +108,10 @@ public class ErfApprovalCycleReportServiceImpl implements ErfApprovalCycleReport log.info("=== 导出审批周期报表 ==="); try { List reportList = erfExpApplyMapper.getApprovalCycleReportList(data); + + // 导出与列表页保持同一套审批字段计算逻辑 + enrichApprovalFields(reportList); + reportList.forEach(this::calculateApprovalCycle); reportList = filterByAllApproveDate(reportList, data); reportList = filterByCycleDays(reportList, data); @@ -338,6 +360,250 @@ public class ErfApprovalCycleReportServiceImpl implements ErfApprovalCycleReport cell.setCellStyle(cellStyle); } + /** + * 重新计算审批相关字段(审批人、审批时间) + * + *

背景:流程节点实例表采用联合主键(apply_no,node_code,attempt_no),多人审批会产生多条不同attempt_no记录。 + * 报表不能只取MAX(attempt_no),否则会把“最后创建但未审批”的节点当成当前结果,导致已审批却显示待审批。

+ * + *

处理规则:

+ *
    + *
  • 按流程remark中的审批人数,截取该节点最新N条记录作为“当前批次”
  • + *
  • 优先展示当前批次内已批准/已完成的审批人和审批时间
  • + *
  • 若当前批次尚无人审批,展示候选审批人,审批时间置空
  • + *
+ */ + private void enrichApprovalFields(List reportList) { + if (reportList == null || reportList.isEmpty()) { + return; + } + + Map configCache = new HashMap<>(); + Map> nodeCache = new HashMap<>(); + + for (ErfApprovalCycleReportDto dto : reportList) { + if (dto == null || isBlank(dto.getApplyNo())) { + continue; + } + String applyNo = dto.getApplyNo(); + ManagerBatchConfig config = configCache.computeIfAbsent(applyNo, this::loadManagerBatchConfig); + + ApprovalSnapshot techSnapshot = resolveApprovalSnapshot( + getCurrentBatchNodes(applyNo, "技术经理审批", 1, nodeCache), true); + dto.setTechManagerName(techSnapshot.getApproverNames()); + dto.setTechApproveTime(techSnapshot.getApproveTime()); + + ApprovalSnapshot prodSnapshot = resolveApprovalSnapshot( + getCurrentBatchNodes(applyNo, "生产经理审批", config.getProdManagerCount(), nodeCache), + config.isProdManagerAnyApproved()); + dto.setProdManagerName(prodSnapshot.getApproverNames()); + dto.setProdApproveTime(prodSnapshot.getApproveTime()); + + ApprovalSnapshot qualSnapshot = resolveApprovalSnapshot( + getCurrentBatchNodes(applyNo, "质量经理审批", config.getQualityManagerCount(), nodeCache), + config.isQualityManagerAnyApproved()); + dto.setQualManagerName(qualSnapshot.getApproverNames()); + dto.setQualApproveTime(qualSnapshot.getApproveTime()); + } + } + + /** + * 加载经理审批批次配置(来源:erf_flow_instance.remark) + */ + private ManagerBatchConfig loadManagerBatchConfig(String applyNo) { + ManagerBatchConfig config = new ManagerBatchConfig(); + ErfFlowInstance flowInstance = erfFlowInstanceMapper.selectById(applyNo); + if (flowInstance == null || isBlank(flowInstance.getRemark())) { + return config; + } + + try { + JsonNode root = JSON_MAPPER.readTree(flowInstance.getRemark()); + config.setProdManagerAnyApproved(root.path("prodManagerAnyApproved").asBoolean(false)); + config.setQualityManagerAnyApproved(root.path("qualityManagerAnyApproved").asBoolean(false)); + + JsonNode prodArray = root.path("prodManagerIds"); + if (prodArray.isArray()) { + config.setProdManagerCount(prodArray.size()); + } else if (root.path("prodManagerId").asLong(0L) > 0) { + config.setProdManagerCount(1); + } + + JsonNode qualityArray = root.path("qualityManagerIds"); + if (qualityArray.isArray()) { + config.setQualityManagerCount(qualityArray.size()); + } else if (root.path("qualityManagerId").asLong(0L) > 0) { + config.setQualityManagerCount(1); + } + } catch (Exception e) { + log.warn("解析经理审批批次配置失败,applyNo={}, remark={}, error={}", + applyNo, flowInstance.getRemark(), e.getMessage()); + } + + return config; + } + + /** + * 获取指定节点“当前批次”记录 + * + *

按attempt_no倒序读取全部节点;若expectedCount>0,取最新expectedCount条作为当前批次。

+ */ + private List getCurrentBatchNodes(String applyNo, + String nodeCode, + int expectedCount, + Map> nodeCache) { + String cacheKey = applyNo + "|" + nodeCode; + List allNodes = nodeCache.computeIfAbsent(cacheKey, key -> { + QueryWrapper query = new QueryWrapper<>(); + query.eq("apply_no", applyNo) + .eq("node_code", nodeCode) + .orderByDesc("attempt_no"); + return erfFlowNodeInstanceMapper.selectList(query); + }); + + if (allNodes == null || allNodes.isEmpty()) { + return Collections.emptyList(); + } + + if (expectedCount > 0 && allNodes.size() > expectedCount) { + return new ArrayList<>(allNodes.subList(0, expectedCount)); + } + + return allNodes; + } + + /** + * 从节点列表中提取展示快照 + */ + private ApprovalSnapshot resolveApprovalSnapshot(List nodes, boolean anyApprovedRule) { + if (nodes == null || nodes.isEmpty()) { + return ApprovalSnapshot.empty(); + } + + List validNodes = nodes.stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (validNodes.isEmpty()) { + return ApprovalSnapshot.empty(); + } + + List approvedNodes = validNodes.stream() + .filter(node -> "已批准".equals(node.getStatus()) || "已完成".equals(node.getStatus())) + .collect(Collectors.toList()); + + String approvedNames = joinDistinctNames(approvedNodes); + Date latestApproveTime = approvedNodes.stream() + .map(ErfFlowNodeInstance::getCompleteTime) + .filter(Objects::nonNull) + .max(Date::compareTo) + .orElse(null); + + if (anyApprovedRule) { + if (!approvedNodes.isEmpty()) { + return new ApprovalSnapshot(approvedNames, latestApproveTime); + } + String candidateNames = joinDistinctNames(validNodes); + return new ApprovalSnapshot(candidateNames, null); + } + + // 全部通过策略:必须当前批次全部已批准/已完成,才给审批时间 + boolean allApproved = validNodes.stream() + .allMatch(node -> "已批准".equals(node.getStatus()) || "已完成".equals(node.getStatus())); + if (allApproved && !approvedNodes.isEmpty()) { + return new ApprovalSnapshot(approvedNames, latestApproveTime); + } + + // 未全部完成时仅回显已审批人(无则显示候选人),审批时间保持待审批 + if (!approvedNodes.isEmpty()) { + return new ApprovalSnapshot(approvedNames, null); + } + String candidateNames = joinDistinctNames(validNodes); + return new ApprovalSnapshot(candidateNames, null); + } + + /** + * 拼接去重审批人名称 + */ + private String joinDistinctNames(List nodes) { + String names = nodes.stream() + .map(ErfFlowNodeInstance::getAssigneeName) + .filter(name -> !isBlank(name)) + .distinct() + .collect(Collectors.joining("、")); + return isBlank(names) ? null : names; + } + + private boolean isBlank(String text) { + return text == null || text.trim().isEmpty(); + } + + /** + * 当前批次配置 + */ + private static class ManagerBatchConfig { + private int prodManagerCount; + private int qualityManagerCount; + private boolean prodManagerAnyApproved; + private boolean qualityManagerAnyApproved; + + public int getProdManagerCount() { + return prodManagerCount; + } + + public void setProdManagerCount(int prodManagerCount) { + this.prodManagerCount = prodManagerCount; + } + + public int getQualityManagerCount() { + return qualityManagerCount; + } + + public void setQualityManagerCount(int qualityManagerCount) { + this.qualityManagerCount = qualityManagerCount; + } + + public boolean isProdManagerAnyApproved() { + return prodManagerAnyApproved; + } + + public void setProdManagerAnyApproved(boolean prodManagerAnyApproved) { + this.prodManagerAnyApproved = prodManagerAnyApproved; + } + + public boolean isQualityManagerAnyApproved() { + return qualityManagerAnyApproved; + } + + public void setQualityManagerAnyApproved(boolean qualityManagerAnyApproved) { + this.qualityManagerAnyApproved = qualityManagerAnyApproved; + } + } + + /** + * 审批快照 + */ + private static class ApprovalSnapshot { + private final String approverNames; + private final Date approveTime; + + private ApprovalSnapshot(String approverNames, Date approveTime) { + this.approverNames = approverNames; + this.approveTime = approveTime; + } + + public static ApprovalSnapshot empty() { + return new ApprovalSnapshot(null, null); + } + + public String getApproverNames() { + return approverNames; + } + + public Date getApproveTime() { + return approveTime; + } + } + /** * 计算审批周期相关字段 *