feat(wms): 添加钢卷加工链追溯功能
- 在IWmsMaterialCoilService中新增queryCoilChain方法实现双向追溯
- 在WmsMaterialCoilController中添加/chain/all/{coilId}接口
- 在WmsMaterialCoilMapper中新增selectByParentCoilIds批量查询方法
- 在Mapper XML中实现FIND_IN_SET匹配逗号分隔的parent_coil_id查询
- 实现完整的双向追溯逻辑:向上追溯祖先向下查找后代支持合卷场景
- 创建CoilChainVo数据传输对象包含追溯结果和节点关系信息
- 实现BFS算法构建完整的加工链父子关系映射和深度计算
This commit is contained in:
@@ -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<com.klp.domain.vo.CoilChainVo> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增钢卷物料表
|
||||
*/
|
||||
|
||||
55
klp-wms/src/main/java/com/klp/domain/vo/CoilChainVo.java
Normal file
55
klp-wms/src/main/java/com/klp/domain/vo/CoilChainVo.java
Normal file
@@ -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<CoilChainNode> ancestors;
|
||||
|
||||
/**
|
||||
* 后代钢卷列表(所有子孙节点)
|
||||
*/
|
||||
private List<CoilChainNode> descendants;
|
||||
|
||||
/**
|
||||
* 加工链扁平列表(按加工顺序排列:根 → ... → 当前 → ... → 叶子)
|
||||
* 每个节点标注 depth(0=根,正数向下)和 relation
|
||||
*/
|
||||
private List<CoilChainNode> traceList;
|
||||
|
||||
/**
|
||||
* 链条上钢卷总数
|
||||
*/
|
||||
private int totalCount;
|
||||
|
||||
/**
|
||||
* 加工链节点
|
||||
*/
|
||||
@Data
|
||||
public static class CoilChainNode {
|
||||
/** 钢卷信息 */
|
||||
private WmsMaterialCoilVo coil;
|
||||
/** 相对当前钢卷的深度:负数=祖先,0=自身,正数=后代 */
|
||||
private int depth;
|
||||
/** 节点关系类型 */
|
||||
private String relation;
|
||||
/** 此节点的直接父级ID列表(逗号分隔时会有多个) */
|
||||
private String parentCoilId;
|
||||
}
|
||||
}
|
||||
@@ -163,5 +163,14 @@ public interface WmsMaterialCoilMapper extends BaseMapperPlus<WmsMaterialCoilMap
|
||||
* @return 材质信息
|
||||
*/
|
||||
String selectEarliestHotRolledMaterial(@Param("enterCoilNo") String enterCoilNo);
|
||||
|
||||
/**
|
||||
* 根据父级钢卷ID列表批量查询子钢卷
|
||||
* 使用FIND_IN_SET匹配逗号分隔的parent_coil_id字段
|
||||
*
|
||||
* @param coilIds 父级钢卷ID列表
|
||||
* @return 子钢卷列表
|
||||
*/
|
||||
List<WmsMaterialCoil> selectByParentCoilIds(@Param("coilIds") java.util.Collection<Long> coilIds);
|
||||
}
|
||||
|
||||
|
||||
@@ -417,5 +417,17 @@ public interface IWmsMaterialCoilService {
|
||||
* @param pageQuery 分页参数
|
||||
*/
|
||||
TableDataInfo<WmsMaterialCoilVo> queryPageListWithQrcode(WmsMaterialCoilBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 钢卷加工链追溯查询
|
||||
* 根据钢卷ID双向追溯完整加工链条:
|
||||
* - 向上追溯:沿parentCoilId一直查到根节点(无父级为止)
|
||||
* - 向下追溯:查找所有以当前钢卷为父级的后代钢卷
|
||||
* 支持合卷场景(parentCoilId逗号分隔多父级)
|
||||
*
|
||||
* @param coilId 钢卷ID
|
||||
* @return 加工链追溯结果,包含祖先、自身、后代及扁平化traceList
|
||||
*/
|
||||
com.klp.domain.vo.CoilChainVo queryCoilChain(Long coilId);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Long, WmsMaterialCoil> allCoils = new LinkedHashMap<>();
|
||||
allCoils.put(coilId, startEntity);
|
||||
|
||||
Set<Long> ancestorIds = new LinkedHashSet<>(); // 祖先ID集合
|
||||
List<Long> ancestorOrder = new ArrayList<>(); // 追溯顺序:直接父级→根
|
||||
|
||||
// 2. 向上追溯祖先(沿parentCoilId链)
|
||||
Set<Long> parentIds = parseParentCoilIds(startEntity.getParentCoilId());
|
||||
int maxDepth = 50; // 防止无限循环
|
||||
while (!parentIds.isEmpty() && maxDepth-- > 0) {
|
||||
Set<Long> 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<Long> descendantIds = new LinkedHashSet<>();
|
||||
Map<Long, List<Long>> parentToChildren = new LinkedHashMap<>(); // parentId -> childIds
|
||||
|
||||
// BFS队列:从当前钢卷开始
|
||||
Set<Long> currentLevel = new LinkedHashSet<>();
|
||||
currentLevel.add(coilId);
|
||||
// 祖先也要参与BFS(它们可能也有其他后代分支)
|
||||
for (Long aid : ancestorOrder) {
|
||||
currentLevel.add(aid);
|
||||
}
|
||||
Set<Long> bfsVisited = new LinkedHashSet<>(allCoils.keySet());
|
||||
|
||||
maxDepth = 50;
|
||||
while (!currentLevel.isEmpty() && maxDepth-- > 0) {
|
||||
List<Long> levelList = new ArrayList<>(currentLevel);
|
||||
List<WmsMaterialCoil> children = baseMapper.selectByParentCoilIds(levelList);
|
||||
currentLevel.clear();
|
||||
|
||||
if (children != null && !children.isEmpty()) {
|
||||
for (WmsMaterialCoil child : children) {
|
||||
Long cid = child.getCoilId();
|
||||
// 确定子节点属于哪个父节点
|
||||
Set<Long> 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<Long, WmsMaterialCoil> entry : allCoils.entrySet()) {
|
||||
Long cid = entry.getKey();
|
||||
WmsMaterialCoil coil = entry.getValue();
|
||||
Set<Long> parents = parseParentCoilIds(coil.getParentCoilId());
|
||||
for (Long pid : parents) {
|
||||
if (allCoils.containsKey(pid)) {
|
||||
parentToChildren
|
||||
.computeIfAbsent(pid, k -> new ArrayList<>())
|
||||
.add(cid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 计算每个节点相对当前钢卷的深度
|
||||
// 先找所有根节点(parentCoilId为空或父级不在allCoils中的节点)
|
||||
Set<Long> roots = new LinkedHashSet<>();
|
||||
for (Map.Entry<Long, WmsMaterialCoil> entry : allCoils.entrySet()) {
|
||||
Long cid = entry.getKey();
|
||||
WmsMaterialCoil coil = entry.getValue();
|
||||
Set<Long> 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<Long, Integer> absoluteDepth = new LinkedHashMap<>();
|
||||
Queue<Long> depthQueue = new LinkedList<>();
|
||||
Set<Long> 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<Long> 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<Long, Integer> finalDepth = new LinkedHashMap<>();
|
||||
for (Map.Entry<Long, Integer> entry : absoluteDepth.entrySet()) {
|
||||
finalDepth.put(entry.getKey(), entry.getValue() - selfAbsoluteDepth);
|
||||
}
|
||||
// 确保self深度为0
|
||||
finalDepth.put(coilId, 0);
|
||||
|
||||
// 5. 构建响应:所有entity转VO并批量填充关联对象
|
||||
List<WmsMaterialCoilVo> allVos = new ArrayList<>();
|
||||
Map<Long, WmsMaterialCoilVo> 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<CoilChainVo.CoilChainNode> 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<CoilChainVo.CoilChainNode> 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<CoilChainVo.CoilChainNode> 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<Long> parseParentCoilIds(String parentCoilId) {
|
||||
Set<Long> 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查询)
|
||||
*/
|
||||
|
||||
@@ -1101,5 +1101,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
${ew.customSqlSegment}
|
||||
</select>
|
||||
|
||||
<!-- 根据父级钢卷ID列表批量查询子钢卷(使用FIND_IN_SET匹配逗号分隔的parent_coil_id) -->
|
||||
<select id="selectByParentCoilIds" resultMap="WmsMaterialCoilResult">
|
||||
SELECT * FROM wms_material_coil
|
||||
WHERE del_flag = 0
|
||||
AND parent_coil_id IS NOT NULL
|
||||
AND parent_coil_id != ''
|
||||
AND (
|
||||
<foreach collection="coilIds" item="id" separator=" OR ">
|
||||
FIND_IN_SET(#{id}, parent_coil_id) > 0
|
||||
</foreach>
|
||||
)
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user