package cn.iocoder.yudao.module.mes.service.trace; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; 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; import cn.iocoder.yudao.module.mes.controller.pub.trace.vo.MesProTracePublicRespVO; import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchWorkerDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.scan.MesProScanEventDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityIndicatorDO; import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum; import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum; import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchWorkerTypeEnum; import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; import cn.iocoder.yudao.module.mes.service.pro.bagcode.MesProBagCodeService; import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService; import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService; import cn.iocoder.yudao.module.mes.service.pro.scan.MesProScanEventService; import cn.iocoder.yudao.module.mes.service.qc.seedquality.MesQcSeedQualityService; import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_TRACE_CODE_NOT_FOUND; /** * 公开扫码溯源 Service 实现 *
* 追溯码解析顺序:袋码 → 托盘码 → 批次码(含临时批次码)。以生产批次为主数据源聚合全程信息。
*/
@Service
@Validated
@Slf4j
public class MesProTracePublicServiceImpl implements MesProTracePublicService {
/**
* H5 袋码列表展示上限(全量统计 + 限量明细)
*/
private static final int BAG_LIST_LIMIT = 100;
@Resource
private MesProBagCodeService bagCodeService;
@Resource
private MesProPalletService palletService;
@Resource
private MesProBatchService batchService;
@Resource
private MesProScanEventService scanEventService;
@Resource
private MesQcSeedQualityService qcSeedQualityService;
@Resource
private MesMdmItemService mdmItemService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private MesWmBatchService mesWmBatchService;
@Override
public MesProTracePublicRespVO queryTrace(String code) {
String rawCode = StrUtil.trim(StrUtil.nullToEmpty(code));
String traceCode = normalize(rawCode);
if (StrUtil.isEmpty(traceCode)) {
throw exception(MES_TRACE_CODE_NOT_FOUND);
}
// 1. 解析追溯入口,定位批次
TraceEntry entry = resolveEntry(traceCode);
if (entry.batchId == null) {
throw exception(MES_TRACE_CODE_NOT_FOUND);
}
// 2. 聚合全程信息
return buildRespVO(rawCode, traceCode, entry.sourceType, entry.batchId);
}
// ==================== 追溯码解析 ====================
/**
* 兼容整 URL 扫码(二维码内容为 H5 地址时),仅提取 code 参数;否则原样返回
*/
private String normalize(String code) {
if (StrUtil.startWithIgnoreCase(code, "http")) {
String query = StrUtil.subAfter(code, "?", false);
if (StrUtil.isNotEmpty(query)) {
// 去掉 # 锚点
query = StrUtil.subBefore(query, "#", false);
for (String pair : StrUtil.split(query, "&")) {
String[] kv = pair.split("=", 2);
if (kv.length == 2 && "code".equals(kv[0])) {
return StrUtil.trim(URLDecoder.decode(kv[1], StandardCharsets.UTF_8));
}
}
}
return "";
}
return code;
}
private TraceEntry resolveEntry(String traceCode) {
// ① 袋码
MesProBagCodeDO bag = bagCodeService.getBagCodeByCode(traceCode);
if (bag != null) {
return new TraceEntry("BAG", bag.getBatchId());
}
// ② 托盘码
MesProPalletDO pallet = palletService.getPalletByCode(traceCode);
if (pallet != null) {
return new TraceEntry("PALLET", pallet.getBatchId());
}
// ③ 批次码(含临时批次码,getBatchByCode 内部兼容)
MesProBatchDO batch = batchService.getBatchByCode(traceCode);
if (batch != null) {
return new TraceEntry("BATCH", batch.getId());
}
return new TraceEntry("", null);
}
// ==================== 聚合构建 ====================
private MesProTracePublicRespVO buildRespVO(String requestCode, String traceCode, String sourceType, Long batchId) {
MesProBatchDO batch = batchService.getBatch(batchId);
if (batch == null) {
throw exception(MES_TRACE_CODE_NOT_FOUND);
}
MesProTracePublicRespVO resp = new MesProTracePublicRespVO();
resp.setSourceType(sourceType);
resp.setRequestCode(requestCode);
// 品种区块
fillItem(resp, batch);
// 批次区块
resp.setBatchCode(batch.getCode()).setTempCode(batch.getTempCode())
.setBatchStatus(batch.getStatus()).setBatchStatusName(batchStatusName(batch.getStatus()))
.setProduceDate(batch.getProduceDate()).setLineCode(batch.getLineCode()).setLineName(batch.getLineName())
.setOwnerUserId(batch.getOwnerUserId()).setOwnerUserName(userName(batch.getOwnerUserId()))
.setPlanQuantity(batch.getPlanQuantity()).setBagQuantity(batch.getBagQuantity())
.setPalletQuantity(batch.getPalletQuantity()).setInboundTime(batch.getInboundTime())
.setRemark(batch.getRemark());
// 人员区块
resp.setWorkers(buildWorkers(batchId));
// 托盘区块
fillPallets(resp, batchId);
// 袋码区块
fillBags(resp, batchId);
// 入库事件区块
resp.setInbound(buildInbound(batchId));
// 质检区块
resp.setQuality(buildQuality(batch));
// 可选扩展区块(有数据才返回)
fillOptionalModules(resp, batch);
return resp;
}
private void fillItem(MesProTracePublicRespVO resp, MesProBatchDO batch) {
if (batch.getItemId() == null) {
return;
}
MesMdmItemDO item = mdmItemService.getItem(batch.getItemId());
if (item == null) {
return;
}
resp.setItemCode(item.getCode()).setItemName(item.getName()).setItemSpecification(item.getSpecification());
}
private List