refactor(wms): 重构钢卷追溯组件以支持合卷展示
- 将原有的线性时间轴改造成支持线性段和合卷并排段的布局结构 - 新增 TraceStepBody 组件用于统一渲染追溯步骤内容 - 在后端服务中添加 traceLayout 字段用于返回前端展示结构 - 实现合卷场景下的多列并排展示功能 - 优化步骤排序逻辑,确保按存储顺序正确排列 - 添加合卷前各卷加工过程的并排展示界面 - 实现合卷汇聚节点的特殊展示效果
This commit is contained in:
@@ -318,7 +318,9 @@ public class WmsMaterialCoilController extends BaseController {
|
||||
|
||||
/**
|
||||
* 钢卷溯源查询
|
||||
* 根据钢卷ID查询二维码,解析content中的steps,然后根据steps中的钢卷号反向查询数据库
|
||||
* 根据钢卷ID查询二维码,解析content中的steps,然后根据steps中的钢卷号反向查询数据库。
|
||||
* 返回 data.steps 为按二维码存储顺序排列的全量步骤(合卷场景下勿依赖原 step 序号排序,已带 display_step);
|
||||
* data.traceLayout 为前端展示结构:linear(单时间线)与 merge_join(合卷前多列并排 + 合卷汇聚)交替。
|
||||
*
|
||||
* @param coilId 钢卷ID
|
||||
* @param currentCoilNo 当前钢卷号(可选参数,用于查询特定子钢卷)
|
||||
|
||||
@@ -99,7 +99,7 @@ public interface IWmsMaterialCoilService {
|
||||
*
|
||||
* @param coilId 钢卷ID
|
||||
* @param currentCoilNo 当前钢卷号(可选,用于查询特定子钢卷)
|
||||
* @return 溯源结果(包含二维码信息和数据库记录)
|
||||
* @return 溯源结果(包含二维码信息、按存储顺序排列的 steps、traceLayout 并排展示结构、数据库记录)
|
||||
*/
|
||||
Map<String, Object> queryTrace(Long coilId, String currentCoilNo);
|
||||
|
||||
|
||||
@@ -2095,10 +2095,12 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
// 合并所有参与合卷的原始钢卷的历史steps
|
||||
List<Map<String, Object>> steps = new ArrayList<>();
|
||||
|
||||
// 从参与合卷的原始钢卷中获取二维码信息并合并
|
||||
// 从参与合卷的原始钢卷中获取二维码信息并合并,并记录每个父卷携带的历史步数(用于溯源并排展示)
|
||||
List<Integer> parentHistStepCounts = new ArrayList<>();
|
||||
if (originalCoils != null && !originalCoils.isEmpty()) {
|
||||
for (WmsMaterialCoilBo originalCoilBo : originalCoils) {
|
||||
if (originalCoilBo.getCoilId() != null) {
|
||||
int sizeBefore = steps.size();
|
||||
// 查询原始钢卷的二维码信息
|
||||
WmsMaterialCoil originalCoil = baseMapper.selectById(originalCoilBo.getCoilId());
|
||||
if (originalCoil != null && originalCoil.getQrcodeRecordId() != null) {
|
||||
@@ -2114,6 +2116,7 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
}
|
||||
}
|
||||
}
|
||||
parentHistStepCounts.add(steps.size() - sizeBefore);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2139,6 +2142,7 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
}
|
||||
mergeStep.put("parent_coil_nos", String.join(",", originalCoilNos));
|
||||
mergeStep.put("parent_coil_ids", String.join(",", originalCoilIds));
|
||||
mergeStep.put("parent_hist_step_counts", parentHistStepCounts);
|
||||
mergeStep.put("new_current_coil_no", mergedCoilBo.getCurrentCoilNo());
|
||||
mergeStep.put("operator", LoginHelper.getUsername()); // 操作者
|
||||
steps.add(mergeStep);
|
||||
@@ -2468,11 +2472,14 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||||
|
||||
// 合卷会把多条产线的 steps 顺序拼接,各线局部 step 均为 1..n,存在重复序号;必须保留 JSON 数组顺序,不能按 step 字段排序
|
||||
List<Map<String, Object>> orderedSteps = steps != null ? new ArrayList<>(steps) : new ArrayList<>();
|
||||
|
||||
Set<String> allCoilNos = new HashSet<>();
|
||||
Set<String> operatorUsernames = new HashSet<>();
|
||||
|
||||
if (steps != null) {
|
||||
for (Map<String, Object> step : steps) {
|
||||
if (!orderedSteps.isEmpty()) {
|
||||
for (Map<String, Object> step : orderedSteps) {
|
||||
extractCoilNo(step, "current_coil_no", allCoilNos);
|
||||
extractCoilNo(step, "new_current_coil_no", allCoilNos);
|
||||
extractCoilNo(step, "old_current_coil_no", allCoilNos);
|
||||
@@ -2493,19 +2500,12 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
|
||||
Map<String, String> operatorNicknameMap = getOperatorNicknames(operatorUsernames);
|
||||
|
||||
List<Map<String, Object>> allSteps = new ArrayList<>(steps != null ? steps : new ArrayList<>());
|
||||
|
||||
allSteps.sort((a, b) -> {
|
||||
Integer stepA = (Integer) a.get("step");
|
||||
Integer stepB = (Integer) b.get("step");
|
||||
if (stepA == null) stepA = 0;
|
||||
if (stepB == null) stepB = 0;
|
||||
return stepA.compareTo(stepB);
|
||||
});
|
||||
List<Map<String, Object>> allSteps = orderedSteps;
|
||||
|
||||
for (int i = 0; i < allSteps.size(); i++) {
|
||||
allSteps.get(i).put("display_step", i + 1);
|
||||
allSteps.get(i).put("original_step", allSteps.get(i).get("step"));
|
||||
allSteps.get(i).put("storage_index", i);
|
||||
allSteps.get(i).put("qrcode_serial", qrRecord.getSerialNumber());
|
||||
allSteps.get(i).put("qrcode_id", qrRecord.getRecordId());
|
||||
|
||||
@@ -2517,6 +2517,8 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, Object>> traceLayout = buildTraceLayout(allSteps);
|
||||
|
||||
Set<String> filteredCoilNos = allCoilNos;
|
||||
if (currentCoilNo != null && !currentCoilNo.trim().isEmpty()) {
|
||||
final String filterValue = currentCoilNo;
|
||||
@@ -2567,6 +2569,7 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("qrcode", qrRecord);
|
||||
resultMap.put("steps", allSteps);
|
||||
resultMap.put("traceLayout", traceLayout);
|
||||
resultMap.put("records", result);
|
||||
|
||||
return resultMap;
|
||||
@@ -2643,6 +2646,181 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
}
|
||||
}
|
||||
|
||||
private static int stepNumberValue(Map<String, Object> step) {
|
||||
Object s = step.get("step");
|
||||
if (s == null) {
|
||||
return 0;
|
||||
}
|
||||
if (s instanceof Number) {
|
||||
return ((Number) s).intValue();
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(s.toString().trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMergeTraceStep(Map<String, Object> step) {
|
||||
Object op = step.get("operation");
|
||||
return op != null && "合卷".equals(op.toString());
|
||||
}
|
||||
|
||||
private static List<String> splitCommaTokens(Object value) {
|
||||
if (value == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.stream(value.toString().split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Integer> readParentHistStepCounts(Map<String, Object> mergeStep) {
|
||||
Object raw = mergeStep.get("parent_hist_step_counts");
|
||||
if (!(raw instanceof List)) {
|
||||
return null;
|
||||
}
|
||||
List<?> list = (List<?>) raw;
|
||||
List<Integer> out = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
if (o instanceof Number) {
|
||||
out.add(((Number) o).intValue());
|
||||
} else if (o != null) {
|
||||
try {
|
||||
out.add(Integer.parseInt(o.toString().trim()));
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
out.add(0);
|
||||
}
|
||||
}
|
||||
return out.isEmpty() ? null : out;
|
||||
}
|
||||
|
||||
private static List<Integer> inferLaneSizesByStepReset(List<Map<String, Object>> buffer) {
|
||||
if (buffer == null || buffer.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<Integer> lengths = new ArrayList<>();
|
||||
int runStart = 0;
|
||||
Integer prev = null;
|
||||
for (int i = 0; i < buffer.size(); i++) {
|
||||
int cur = stepNumberValue(buffer.get(i));
|
||||
if (prev != null && (cur < prev || (cur == 1 && prev > 1))) {
|
||||
lengths.add(i - runStart);
|
||||
runStart = i;
|
||||
}
|
||||
prev = cur;
|
||||
}
|
||||
lengths.add(buffer.size() - runStart);
|
||||
return lengths;
|
||||
}
|
||||
|
||||
private static List<Integer> distributeEvenly(int total, int parts) {
|
||||
if (parts <= 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<Integer> r = new ArrayList<>();
|
||||
int base = total / parts;
|
||||
int rem = total % parts;
|
||||
for (int i = 0; i < parts; i++) {
|
||||
r.add(base + (i < rem ? 1 : 0));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private List<List<Map<String, Object>>> sliceBufferIntoMergeLanes(List<Map<String, Object>> buffer,
|
||||
Map<String, Object> mergeStep) {
|
||||
List<String> parentIds = splitCommaTokens(mergeStep.get("parent_coil_ids"));
|
||||
List<String> parentNos = splitCommaTokens(mergeStep.get("parent_coil_nos"));
|
||||
int n = Math.max(parentIds.size(), parentNos.size());
|
||||
if (n <= 0) {
|
||||
n = 1;
|
||||
}
|
||||
if (buffer == null) {
|
||||
buffer = Collections.emptyList();
|
||||
}
|
||||
int total = buffer.size();
|
||||
List<Integer> counts = readParentHistStepCounts(mergeStep);
|
||||
if (counts == null || counts.size() != n || counts.stream().mapToInt(Integer::intValue).sum() != total) {
|
||||
List<Integer> inferred = inferLaneSizesByStepReset(buffer);
|
||||
if (inferred.size() == n) {
|
||||
counts = inferred;
|
||||
} else {
|
||||
counts = distributeEvenly(total, n);
|
||||
}
|
||||
}
|
||||
List<List<Map<String, Object>>> lanes = new ArrayList<>();
|
||||
int offset = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
int len = i < counts.size() ? Math.max(0, counts.get(i)) : 0;
|
||||
int end = Math.min(offset + len, total);
|
||||
lanes.add(new ArrayList<>(buffer.subList(offset, end)));
|
||||
offset = end;
|
||||
}
|
||||
if (offset < total) {
|
||||
if (!lanes.isEmpty()) {
|
||||
lanes.get(lanes.size() - 1).addAll(buffer.subList(offset, total));
|
||||
} else {
|
||||
lanes.add(new ArrayList<>(buffer.subList(0, total)));
|
||||
}
|
||||
}
|
||||
return lanes;
|
||||
}
|
||||
|
||||
private static List<String> buildMergeLaneLabels(Map<String, Object> mergeStep, int laneCount) {
|
||||
List<String> nos = splitCommaTokens(mergeStep.get("parent_coil_nos"));
|
||||
List<String> ids = splitCommaTokens(mergeStep.get("parent_coil_ids"));
|
||||
List<String> labels = new ArrayList<>();
|
||||
for (int i = 0; i < laneCount; i++) {
|
||||
String no = i < nos.size() ? nos.get(i) : null;
|
||||
String id = i < ids.size() ? ids.get(i) : null;
|
||||
if (StringUtils.isNotBlank(no)) {
|
||||
labels.add(no);
|
||||
} else if (StringUtils.isNotBlank(id)) {
|
||||
labels.add("ID:" + id);
|
||||
} else {
|
||||
labels.add("来源 " + (i + 1));
|
||||
}
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将按存储顺序排列的 steps 拆成「线性段 + 合卷并排段」,供前端展示。
|
||||
*/
|
||||
private List<Map<String, Object>> buildTraceLayout(List<Map<String, Object>> orderedSteps) {
|
||||
List<Map<String, Object>> layout = new ArrayList<>();
|
||||
if (orderedSteps == null || orderedSteps.isEmpty()) {
|
||||
return layout;
|
||||
}
|
||||
List<Map<String, Object>> buffer = new ArrayList<>();
|
||||
for (Map<String, Object> s : orderedSteps) {
|
||||
if (isMergeTraceStep(s)) {
|
||||
Map<String, Object> panel = new HashMap<>();
|
||||
panel.put("type", "merge_join");
|
||||
List<List<Map<String, Object>>> lanes = sliceBufferIntoMergeLanes(new ArrayList<>(buffer), s);
|
||||
panel.put("lanes", lanes);
|
||||
panel.put("laneLabels", buildMergeLaneLabels(s, lanes.size()));
|
||||
panel.put("mergeStep", s);
|
||||
layout.add(panel);
|
||||
buffer.clear();
|
||||
} else {
|
||||
buffer.add(s);
|
||||
}
|
||||
}
|
||||
if (!buffer.isEmpty()) {
|
||||
Map<String, Object> linear = new HashMap<>();
|
||||
linear.put("type", "linear");
|
||||
linear.put("steps", new ArrayList<>(buffer));
|
||||
layout.add(linear);
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询各个库区中不同类型的钢卷分布情况
|
||||
* 按库区分组,统计每种物品类型和物品ID的钢卷数量和重量
|
||||
|
||||
Reference in New Issue
Block a user