|
|
@ -2,13 +2,20 @@ package com.xujie.sys.modules.erf.service.impl; |
|
|
|
|
|
|
|
|
import com.alibaba.excel.EasyExcel; |
|
|
import com.alibaba.excel.EasyExcel; |
|
|
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; |
|
|
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.PageHelper; |
|
|
import com.github.pagehelper.PageInfo; |
|
|
import com.github.pagehelper.PageInfo; |
|
|
import com.xujie.sys.common.utils.PageUtils; |
|
|
import com.xujie.sys.common.utils.PageUtils; |
|
|
import com.xujie.sys.modules.erf.data.ErfApprovalCycleReportExportData; |
|
|
import com.xujie.sys.modules.erf.data.ErfApprovalCycleReportExportData; |
|
|
import com.xujie.sys.modules.erf.data.ErfApprovalCycleQueryData; |
|
|
import com.xujie.sys.modules.erf.data.ErfApprovalCycleQueryData; |
|
|
import com.xujie.sys.modules.erf.dto.ErfApprovalCycleReportDto; |
|
|
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.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 com.xujie.sys.modules.erf.service.ErfApprovalCycleReportService; |
|
|
import jakarta.servlet.http.HttpServletResponse; |
|
|
import jakarta.servlet.http.HttpServletResponse; |
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
@ -35,11 +42,14 @@ import java.time.LocalDateTime; |
|
|
import java.time.ZoneId; |
|
|
import java.time.ZoneId; |
|
|
import java.time.temporal.ChronoUnit; |
|
|
import java.time.temporal.ChronoUnit; |
|
|
import java.time.format.DateTimeFormatter; |
|
|
import java.time.format.DateTimeFormatter; |
|
|
|
|
|
import java.util.ArrayList; |
|
|
|
|
|
import java.util.Collections; |
|
|
import java.util.Comparator; |
|
|
import java.util.Comparator; |
|
|
import java.util.Date; |
|
|
import java.util.Date; |
|
|
import java.util.HashMap; |
|
|
import java.util.HashMap; |
|
|
import java.util.List; |
|
|
import java.util.List; |
|
|
import java.util.Map; |
|
|
import java.util.Map; |
|
|
|
|
|
import java.util.Objects; |
|
|
import java.util.stream.Collectors; |
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -60,12 +70,17 @@ import java.util.stream.Collectors; |
|
|
@Service |
|
|
@Service |
|
|
public class ErfApprovalCycleReportServiceImpl implements ErfApprovalCycleReportService { |
|
|
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 HEADER_ROW_COUNT = 2; |
|
|
private static final int COL_APPROVAL_CYCLE_DAYS = 13; |
|
|
private static final int COL_APPROVAL_CYCLE_DAYS = 13; |
|
|
private static final int COL_APPROVE_STATUS = 14; |
|
|
private static final int COL_APPROVE_STATUS = 14; |
|
|
|
|
|
|
|
|
@Autowired |
|
|
@Autowired |
|
|
private ErfExpApplyMapper erfExpApplyMapper; |
|
|
private ErfExpApplyMapper erfExpApplyMapper; |
|
|
|
|
|
@Autowired |
|
|
|
|
|
private ErfFlowNodeInstanceMapper erfFlowNodeInstanceMapper; |
|
|
|
|
|
@Autowired |
|
|
|
|
|
private ErfFlowInstanceMapper erfFlowInstanceMapper; |
|
|
|
|
|
|
|
|
@Override |
|
|
@Override |
|
|
public PageUtils queryPage(ErfApprovalCycleQueryData data) { |
|
|
public PageUtils queryPage(ErfApprovalCycleQueryData data) { |
|
|
@ -76,6 +91,9 @@ public class ErfApprovalCycleReportServiceImpl implements ErfApprovalCycleReport |
|
|
List<ErfApprovalCycleReportDto> reportList = erfExpApplyMapper.getApprovalCycleReportList(data); |
|
|
List<ErfApprovalCycleReportDto> reportList = erfExpApplyMapper.getApprovalCycleReportList(data); |
|
|
PageInfo<ErfApprovalCycleReportDto> pageInfo = new PageInfo<>(reportList); |
|
|
PageInfo<ErfApprovalCycleReportDto> pageInfo = new PageInfo<>(reportList); |
|
|
|
|
|
|
|
|
|
|
|
// 重新按当前审批批次计算各角色审批人和审批时间 |
|
|
|
|
|
enrichApprovalFields(reportList); |
|
|
|
|
|
|
|
|
// 计算审批周期及完成状态 |
|
|
// 计算审批周期及完成状态 |
|
|
reportList.forEach(this::calculateApprovalCycle); |
|
|
reportList.forEach(this::calculateApprovalCycle); |
|
|
reportList = filterByAllApproveDate(reportList, data); |
|
|
reportList = filterByAllApproveDate(reportList, data); |
|
|
@ -90,6 +108,10 @@ public class ErfApprovalCycleReportServiceImpl implements ErfApprovalCycleReport |
|
|
log.info("=== 导出审批周期报表 ==="); |
|
|
log.info("=== 导出审批周期报表 ==="); |
|
|
try { |
|
|
try { |
|
|
List<ErfApprovalCycleReportDto> reportList = erfExpApplyMapper.getApprovalCycleReportList(data); |
|
|
List<ErfApprovalCycleReportDto> reportList = erfExpApplyMapper.getApprovalCycleReportList(data); |
|
|
|
|
|
|
|
|
|
|
|
// 导出与列表页保持同一套审批字段计算逻辑 |
|
|
|
|
|
enrichApprovalFields(reportList); |
|
|
|
|
|
|
|
|
reportList.forEach(this::calculateApprovalCycle); |
|
|
reportList.forEach(this::calculateApprovalCycle); |
|
|
reportList = filterByAllApproveDate(reportList, data); |
|
|
reportList = filterByAllApproveDate(reportList, data); |
|
|
reportList = filterByCycleDays(reportList, data); |
|
|
reportList = filterByCycleDays(reportList, data); |
|
|
@ -338,6 +360,250 @@ public class ErfApprovalCycleReportServiceImpl implements ErfApprovalCycleReport |
|
|
cell.setCellStyle(cellStyle); |
|
|
cell.setCellStyle(cellStyle); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 重新计算审批相关字段(审批人、审批时间) |
|
|
|
|
|
* |
|
|
|
|
|
* <p><b>背景:</b>流程节点实例表采用联合主键(apply_no,node_code,attempt_no),多人审批会产生多条不同attempt_no记录。 |
|
|
|
|
|
* 报表不能只取MAX(attempt_no),否则会把“最后创建但未审批”的节点当成当前结果,导致已审批却显示待审批。</p> |
|
|
|
|
|
* |
|
|
|
|
|
* <p><b>处理规则:</b></p> |
|
|
|
|
|
* <ul> |
|
|
|
|
|
* <li>按流程remark中的审批人数,截取该节点最新N条记录作为“当前批次”</li> |
|
|
|
|
|
* <li>优先展示当前批次内已批准/已完成的审批人和审批时间</li> |
|
|
|
|
|
* <li>若当前批次尚无人审批,展示候选审批人,审批时间置空</li> |
|
|
|
|
|
* </ul> |
|
|
|
|
|
*/ |
|
|
|
|
|
private void enrichApprovalFields(List<ErfApprovalCycleReportDto> reportList) { |
|
|
|
|
|
if (reportList == null || reportList.isEmpty()) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Map<String, ManagerBatchConfig> configCache = new HashMap<>(); |
|
|
|
|
|
Map<String, List<ErfFlowNodeInstance>> 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; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 获取指定节点“当前批次”记录 |
|
|
|
|
|
* |
|
|
|
|
|
* <p>按attempt_no倒序读取全部节点;若expectedCount>0,取最新expectedCount条作为当前批次。</p> |
|
|
|
|
|
*/ |
|
|
|
|
|
private List<ErfFlowNodeInstance> getCurrentBatchNodes(String applyNo, |
|
|
|
|
|
String nodeCode, |
|
|
|
|
|
int expectedCount, |
|
|
|
|
|
Map<String, List<ErfFlowNodeInstance>> nodeCache) { |
|
|
|
|
|
String cacheKey = applyNo + "|" + nodeCode; |
|
|
|
|
|
List<ErfFlowNodeInstance> allNodes = nodeCache.computeIfAbsent(cacheKey, key -> { |
|
|
|
|
|
QueryWrapper<ErfFlowNodeInstance> 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<ErfFlowNodeInstance> nodes, boolean anyApprovedRule) { |
|
|
|
|
|
if (nodes == null || nodes.isEmpty()) { |
|
|
|
|
|
return ApprovalSnapshot.empty(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
List<ErfFlowNodeInstance> validNodes = nodes.stream() |
|
|
|
|
|
.filter(Objects::nonNull) |
|
|
|
|
|
.collect(Collectors.toList()); |
|
|
|
|
|
if (validNodes.isEmpty()) { |
|
|
|
|
|
return ApprovalSnapshot.empty(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
List<ErfFlowNodeInstance> 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<ErfFlowNodeInstance> 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; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 计算审批周期相关字段 |
|
|
* 计算审批周期相关字段 |
|
|
* |
|
|
* |
|
|
|