diff --git a/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java b/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java index 4628b1e5..ee8d82e2 100644 --- a/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java +++ b/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java @@ -369,6 +369,24 @@ public class WmsMaterialCoilController extends BaseController { return R.ok(iWmsMaterialCoilService.queryById(coilId)); } + /** + * 钢卷加工链追溯查询 + * 根据钢卷ID双向追溯完整加工链条: + * - 向上:沿parentCoilId一直查到根节点 + * - 向下:查找所有后代钢卷 + * + * @param coilId 钢卷ID + */ + @GetMapping("/chain/all/{coilId}") + public R getChain(@NotNull(message = "主键不能为空") + @PathVariable("coilId") Long coilId) { + com.klp.domain.vo.CoilChainVo result = iWmsMaterialCoilService.queryCoilChain(coilId); + if (result == null) { + return R.fail("钢卷不存在"); + } + return R.ok(result); + } + /** * 新增钢卷物料表 */ diff --git a/klp-wms/src/main/java/com/klp/domain/vo/CoilChainVo.java b/klp-wms/src/main/java/com/klp/domain/vo/CoilChainVo.java new file mode 100644 index 00000000..b6abb929 --- /dev/null +++ b/klp-wms/src/main/java/com/klp/domain/vo/CoilChainVo.java @@ -0,0 +1,55 @@ +package com.klp.domain.vo; + +import lombok.Data; +import java.util.List; + +/** + * 钢卷加工链追溯结果 + * + * @author Joshi + * @date 2026-06-17 + */ +@Data +public class CoilChainVo { + + /** + * 当前查询的钢卷 + */ + private WmsMaterialCoilVo self; + + /** + * 祖先钢卷列表(从直接父级 → 根,按追溯顺序排列) + */ + private List ancestors; + + /** + * 后代钢卷列表(所有子孙节点) + */ + private List descendants; + + /** + * 加工链扁平列表(按加工顺序排列:根 → ... → 当前 → ... → 叶子) + * 每个节点标注 depth(0=根,正数向下)和 relation + */ + private List traceList; + + /** + * 链条上钢卷总数 + */ + private int totalCount; + + /** + * 加工链节点 + */ + @Data + public static class CoilChainNode { + /** 钢卷信息 */ + private WmsMaterialCoilVo coil; + /** 相对当前钢卷的深度:负数=祖先,0=自身,正数=后代 */ + private int depth; + /** 节点关系类型 */ + private String relation; + /** 此节点的直接父级ID列表(逗号分隔时会有多个) */ + private String parentCoilId; + } +} diff --git a/klp-wms/src/main/java/com/klp/mapper/WmsMaterialCoilMapper.java b/klp-wms/src/main/java/com/klp/mapper/WmsMaterialCoilMapper.java index 53e8343d..c3753533 100644 --- a/klp-wms/src/main/java/com/klp/mapper/WmsMaterialCoilMapper.java +++ b/klp-wms/src/main/java/com/klp/mapper/WmsMaterialCoilMapper.java @@ -163,5 +163,14 @@ public interface WmsMaterialCoilMapper extends BaseMapperPlus selectByParentCoilIds(@Param("coilIds") java.util.Collection coilIds); } diff --git a/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java b/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java index b68e7e73..0f3d8d9f 100644 --- a/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java +++ b/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java @@ -417,5 +417,17 @@ public interface IWmsMaterialCoilService { * @param pageQuery 分页参数 */ TableDataInfo queryPageListWithQrcode(WmsMaterialCoilBo bo, PageQuery pageQuery); + + /** + * 钢卷加工链追溯查询 + * 根据钢卷ID双向追溯完整加工链条: + * - 向上追溯:沿parentCoilId一直查到根节点(无父级为止) + * - 向下追溯:查找所有以当前钢卷为父级的后代钢卷 + * 支持合卷场景(parentCoilId逗号分隔多父级) + * + * @param coilId 钢卷ID + * @return 加工链追溯结果,包含祖先、自身、后代及扁平化traceList + */ + com.klp.domain.vo.CoilChainVo queryCoilChain(Long coilId); } diff --git a/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java b/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java index 1f60790a..6c07a2c2 100644 --- a/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java +++ b/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java @@ -105,6 +105,242 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService { return vo; } + /** + * 钢卷加工链追溯查询 + * 双向追溯:向上沿parentCoilId查至根节点,向下查所有后代节点 + */ + @Override + public CoilChainVo queryCoilChain(Long coilId) { + // 1. 查询起始钢卷 + WmsMaterialCoil startEntity = baseMapper.selectById(coilId); + if (startEntity == null) { + return null; + } + + // 所有已查询到的钢卷(coilId -> entity) + Map allCoils = new LinkedHashMap<>(); + allCoils.put(coilId, startEntity); + + Set ancestorIds = new LinkedHashSet<>(); // 祖先ID集合 + List ancestorOrder = new ArrayList<>(); // 追溯顺序:直接父级→根 + + // 2. 向上追溯祖先(沿parentCoilId链) + Set parentIds = parseParentCoilIds(startEntity.getParentCoilId()); + int maxDepth = 50; // 防止无限循环 + while (!parentIds.isEmpty() && maxDepth-- > 0) { + Set nextParents = new LinkedHashSet<>(); + for (Long pid : parentIds) { + if (allCoils.containsKey(pid)) { + continue; + } + WmsMaterialCoil parent = baseMapper.selectById(pid); + if (parent != null) { + allCoils.put(pid, parent); + ancestorIds.add(pid); + ancestorOrder.add(pid); + nextParents.addAll(parseParentCoilIds(parent.getParentCoilId())); + } + } + parentIds = nextParents; + } + + // 3. 向下追溯后代(BFS:用FIND_IN_SET批量查子级) + Set descendantIds = new LinkedHashSet<>(); + Map> parentToChildren = new LinkedHashMap<>(); // parentId -> childIds + + // BFS队列:从当前钢卷开始 + Set currentLevel = new LinkedHashSet<>(); + currentLevel.add(coilId); + // 祖先也要参与BFS(它们可能也有其他后代分支) + for (Long aid : ancestorOrder) { + currentLevel.add(aid); + } + Set bfsVisited = new LinkedHashSet<>(allCoils.keySet()); + + maxDepth = 50; + while (!currentLevel.isEmpty() && maxDepth-- > 0) { + List levelList = new ArrayList<>(currentLevel); + List children = baseMapper.selectByParentCoilIds(levelList); + currentLevel.clear(); + + if (children != null && !children.isEmpty()) { + for (WmsMaterialCoil child : children) { + Long cid = child.getCoilId(); + // 确定子节点属于哪个父节点 + Set childParents = parseParentCoilIds(child.getParentCoilId()); + Long matchedParent = null; + for (Long cp : childParents) { + if (levelList.contains(cp)) { + matchedParent = cp; + break; + } + } + if (matchedParent != null) { + parentToChildren + .computeIfAbsent(matchedParent, k -> new ArrayList<>()) + .add(cid); + } + + if (bfsVisited.add(cid)) { + allCoils.put(cid, child); + descendantIds.add(cid); + currentLevel.add(cid); + } + } + } + } + + // 3.1 重建完整的 parentToChildren 映射(清空BFS阶段的部分映射,基于allCoils完整重建) + parentToChildren.clear(); + for (Map.Entry entry : allCoils.entrySet()) { + Long cid = entry.getKey(); + WmsMaterialCoil coil = entry.getValue(); + Set parents = parseParentCoilIds(coil.getParentCoilId()); + for (Long pid : parents) { + if (allCoils.containsKey(pid)) { + parentToChildren + .computeIfAbsent(pid, k -> new ArrayList<>()) + .add(cid); + } + } + } + + // 4. 计算每个节点相对当前钢卷的深度 + // 先找所有根节点(parentCoilId为空或父级不在allCoils中的节点) + Set roots = new LinkedHashSet<>(); + for (Map.Entry entry : allCoils.entrySet()) { + Long cid = entry.getKey(); + WmsMaterialCoil coil = entry.getValue(); + Set parents = parseParentCoilIds(coil.getParentCoilId()); + boolean hasParentInChain = false; + for (Long pid : parents) { + if (allCoils.containsKey(pid)) { + hasParentInChain = true; + break; + } + } + if (!hasParentInChain) { + roots.add(cid); + } + } + + // BFS从根节点开始计算绝对深度 + Map absoluteDepth = new LinkedHashMap<>(); + Queue depthQueue = new LinkedList<>(); + Set depthVisited = new LinkedHashSet<>(); + for (Long rootId : roots) { + depthQueue.offer(rootId); + absoluteDepth.put(rootId, 0); + depthVisited.add(rootId); + } + while (!depthQueue.isEmpty()) { + Long current = depthQueue.poll(); + int currDepth = absoluteDepth.get(current); + List children = parentToChildren.get(current); + if (children != null) { + for (Long childId : children) { + if (depthVisited.add(childId)) { + absoluteDepth.put(childId, currDepth + 1); + depthQueue.offer(childId); + } + } + } + } + + // 转为相对于self的深度 + int selfAbsoluteDepth = absoluteDepth.getOrDefault(coilId, 0); + Map finalDepth = new LinkedHashMap<>(); + for (Map.Entry entry : absoluteDepth.entrySet()) { + finalDepth.put(entry.getKey(), entry.getValue() - selfAbsoluteDepth); + } + // 确保self深度为0 + finalDepth.put(coilId, 0); + + // 5. 构建响应:所有entity转VO并批量填充关联对象 + List allVos = new ArrayList<>(); + Map voMap = new LinkedHashMap<>(); + for (WmsMaterialCoil entity : allCoils.values()) { + WmsMaterialCoilVo vo = new WmsMaterialCoilVo(); + BeanUtil.copyProperties(entity, vo); + allVos.add(vo); + voMap.put(entity.getCoilId(), vo); + } + fillRelatedObjectsBatch(allVos); + + // 6. 组装CoilChainVo + CoilChainVo result = new CoilChainVo(); + result.setSelf(voMap.get(coilId)); + + // ancestors: 从直接父级→根 + List ancestorNodes = new ArrayList<>(); + for (Long aid : ancestorOrder) { + CoilChainVo.CoilChainNode node = new CoilChainVo.CoilChainNode(); + node.setCoil(voMap.get(aid)); + node.setDepth(finalDepth.getOrDefault(aid, 0)); + node.setRelation("ancestor"); + node.setParentCoilId(allCoils.get(aid).getParentCoilId()); + ancestorNodes.add(node); + } + result.setAncestors(ancestorNodes); + + // descendants: 所有后代(BFS顺序) + List descendantNodes = new ArrayList<>(); + for (Long did : descendantIds) { + CoilChainVo.CoilChainNode node = new CoilChainVo.CoilChainNode(); + node.setCoil(voMap.get(did)); + node.setDepth(finalDepth.getOrDefault(did, 0)); + node.setRelation("descendant"); + node.setParentCoilId(allCoils.get(did).getParentCoilId()); + descendantNodes.add(node); + } + result.setDescendants(descendantNodes); + + // traceList: 按深度排序的扁平列表(根→self→叶子) + List traceNodes = new ArrayList<>(); + for (Long cid : allCoils.keySet()) { + CoilChainVo.CoilChainNode node = new CoilChainVo.CoilChainNode(); + node.setCoil(voMap.get(cid)); + int d = finalDepth.getOrDefault(cid, 0); + node.setDepth(d); + if (cid.equals(coilId)) { + node.setRelation("self"); + } else if (ancestorIds.contains(cid)) { + node.setRelation("ancestor"); + } else { + node.setRelation("descendant"); + } + node.setParentCoilId(allCoils.get(cid).getParentCoilId()); + traceNodes.add(node); + } + // 按深度排序(祖先负→自身0→后代正) + traceNodes.sort(Comparator.comparingInt(CoilChainVo.CoilChainNode::getDepth)); + result.setTraceList(traceNodes); + result.setTotalCount(allCoils.size()); + + return result; + } + + /** + * 解析parentCoilId字段,支持逗号分隔的多父级(合卷场景) + */ + private Set parseParentCoilIds(String parentCoilId) { + Set ids = new LinkedHashSet<>(); + if (StringUtils.isBlank(parentCoilId)) { + return ids; + } + for (String s : parentCoilId.split(",")) { + String trimmed = s.trim(); + if (StringUtils.isNotBlank(trimmed)) { + try { + ids.add(Long.parseLong(trimmed)); + } catch (NumberFormatException ignored) { + // 忽略非法格式 + } + } + } + return ids; + } + /** * 批量填充关联对象信息(优化版本,避免N+1查询) */ diff --git a/klp-wms/src/main/resources/mapper/klp/WmsMaterialCoilMapper.xml b/klp-wms/src/main/resources/mapper/klp/WmsMaterialCoilMapper.xml index c8dc757f..369d20d3 100644 --- a/klp-wms/src/main/resources/mapper/klp/WmsMaterialCoilMapper.xml +++ b/klp-wms/src/main/resources/mapper/klp/WmsMaterialCoilMapper.xml @@ -1101,5 +1101,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ${ew.customSqlSegment} + + +