昨天 c89f7dcf9344a35efde2a00edb10491b401ba000
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/trace/MesProTracePublicServiceImpl.java
@@ -2,6 +2,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchFlowRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchProcessTraceRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO;
@@ -38,6 +39,9 @@
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -212,6 +216,7 @@
        fillAntiFake(resp, traceCode);
        // 可选扩展区块(有数据才返回)
        fillOptionalModules(resp, batch);
        fillTimelineAndDecision(resp);
        return resp;
    }
@@ -329,15 +334,59 @@
            resp.setPurchaseSources(CollUtil.isNotEmpty(purchaseSources) ? purchaseSources : List.of());
            resp.setSalesTargets(CollUtil.isNotEmpty(salesTargets) ? salesTargets : List.of());
            resp.setProcessTraces(CollUtil.isNotEmpty(processTraces) ? processTraces : List.of());
            List<MesWmBatchFlowRespVO> seedFlows = mesWmBatchService.getBatchFlowList(batch.getCode());
            resp.setSeedFlows(CollUtil.isNotEmpty(seedFlows) ? seedFlows : List.of());
        } catch (Exception ex) {
            // 仓库批次体系(采购/销售/流转)未启用时容错,不影响核心追溯
            log.warn("[fillOptionalModules][批次 {} 扩展模块查询失败: {}]", batch.getCode(), ex.getMessage());
            resp.setPurchaseSources(List.of());
            resp.setSalesTargets(List.of());
            resp.setProcessTraces(List.of());
            resp.setSeedFlows(List.of());
        }
    }
    private void fillTimelineAndDecision(MesProTracePublicRespVO resp) {
        List<MesProTracePublicRespVO.TimelineVO> timeline = new ArrayList<>();
        if (resp.getProduceDate() != null) {
            timeline.add(timeline("生产", "BATCH_CREATED", "生产批次建立", resp.getProduceDate(), null,
                    resp.getBatchStatusName()));
        }
        if (resp.getProcessTraces() != null) {
            resp.getProcessTraces().forEach(item -> timeline.add(timeline("加工", item.getEventType(),
                    item.getEventType() == null ? "工序流转" : ("IN".equals(item.getEventType()) ? "工序投入" : "工序产出"),
                    item.getEventTime(), item.getQuantity(), item.getQualityStatus() == null ? null : String.valueOf(item.getQualityStatus()))));
        }
        if (resp.getInbound() != null) {
            timeline.add(timeline("仓储", "INBOUND", "仓储入库", resp.getInbound().getEventTime(),
                    resp.getInbound().getQuantity(), resp.getInbound().getStatus()));
        }
        if (resp.getSalesTargets() != null) {
            resp.getSalesTargets().forEach(item -> timeline.add(timeline("销售", "OUTBOUND", "销售出库",
                    item.getSalesDate(), item.getQuantity(), item.getQualityStatus() == null ? null : String.valueOf(item.getQualityStatus()))));
        }
        timeline.sort(Comparator.comparing(MesProTracePublicRespVO.TimelineVO::getEventTime,
                Comparator.nullsLast(Comparator.naturalOrder())));
        resp.setTimeline(timeline);
        MesProTracePublicRespVO.DecisionVO decision = new MesProTracePublicRespVO.DecisionVO();
        decision.setQualityRisk(resp.getQuality() != null && Integer.valueOf(2).equals(resp.getQuality().getResult()));
        decision.setInboundQuantity(resp.getInbound() == null ? null : resp.getInbound().getQuantity());
        decision.setSalesTargetCount(resp.getSalesTargets() == null ? 0 : resp.getSalesTargets().size());
        decision.setConsumerScanCount(resp.getAntiFake() == null ? 0L : resp.getAntiFake().getScanCount());
        decision.setRiskLevel(Boolean.TRUE.equals(decision.getQualityRisk()) ? "HIGH" : "NORMAL");
        resp.setDecision(decision);
    }
    private MesProTracePublicRespVO.TimelineVO timeline(String stage, String eventType, String eventName,
                                                         java.time.LocalDateTime eventTime, BigDecimal quantity,
                                                         String status) {
        MesProTracePublicRespVO.TimelineVO vo = new MesProTracePublicRespVO.TimelineVO();
        vo.setStage(stage).setEventType(eventType).setEventName(eventName).setEventTime(eventTime)
                .setQuantity(quantity).setStatus(status);
        return vo;
    }
    // ==================== 名称映射 ====================
    private String userName(Long userId) {