| src/main/java/com/zy/asrs/controller/OpenController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/asrs/entity/WorkChartAxis.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/asrs/mapper/ReportQueryMapper.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/asrs/service/OpenService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/asrs/service/impl/ExternalTaskFacadeServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/asrs/task/WorkMastScheduler.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/resources/application.yml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/main/java/com/zy/asrs/controller/OpenController.java
@@ -535,7 +535,7 @@ } log.info("[outOrder] cache: {}", JSON.toJSONString(params)); request.setAttribute("cache", params); Map<String, List<OutTaskParam>> linesByBatch = new LinkedHashMap<>(); Map<String, List<OutTaskParam>> linesByBatchSeq = new LinkedHashMap<>(); for (OutTaskParam outTaskParam : params) { if (Cools.isEmpty(outTaskParam) || Cools.isEmpty(outTaskParam.getOrderId())) { return R.error("出库单号不能为空"); @@ -546,10 +546,10 @@ if (Cools.isEmpty(outTaskParam.getStationId())) { return R.error("托盘「" + outTaskParam.getPalletId() + "」出库口编码不能为空"); } linesByBatch.computeIfAbsent(outTaskParam.getBatchSeq(), k -> new ArrayList<>()).add(outTaskParam); linesByBatchSeq.computeIfAbsent(outTaskParam.getBatchSeq(), k -> new ArrayList<>()).add(outTaskParam); } for (Map.Entry<String, List<OutTaskParam>> entry : linesByBatch.entrySet()) { for (Map.Entry<String, List<OutTaskParam>> entry : linesByBatchSeq.entrySet()) { List<OutTaskParam> lines = entry.getValue(); OutTaskParam head = lines.get(0); String oid = head.getOrderId(); @@ -584,8 +584,13 @@ } } List<OutTaskParam> groupedParams = new ArrayList<>(params.size()); for (List<OutTaskParam> lines : linesByBatchSeq.values()) { groupedParams.addAll(lines); } Set<String> seenPallet = new LinkedHashSet<>(); for (OutTaskParam outTaskParam : params) { for (OutTaskParam outTaskParam : groupedParams) { String pid = outTaskParam.getPalletId(); String palletKey = pid == null ? "" : pid; if (!seenPallet.add(palletKey)) { @@ -614,7 +619,7 @@ List<OutTaskParam> missingStock = Lists.newArrayList(); List<OutTaskParam> missingLoc = Lists.newArrayList(); for (OutTaskParam outTaskParam : params) { for (OutTaskParam outTaskParam : groupedParams) { int countLoc = locDetlService.selectCount(new EntityWrapper<LocDetl>().eq("zpallet", outTaskParam.getPalletId())); if (countLoc == 0) { missingStock.add(outTaskParam); @@ -642,7 +647,7 @@ return R.error("没有找到托盘码对应库位:" + String.join(",", badPalletIds)).add(missingLoc); } return openService.outOrderBatch(params); return openService.outOrderBatch(linesByBatchSeq); } /** @@ -692,6 +697,15 @@ private String buildOutOrderBatchKey(OutTaskParam param) { return param.getOrderId() + "#" + param.getBatchSeq(); } /** * 推荐出库站点 */ @PostMapping("/pakoutStaNo") public synchronized R pakoutStaNo(@RequestBody List<String> barcodes) { String StaNo = "1,2,3,4,5"; return R.ok().add(StaNo); } @@ -839,8 +853,8 @@ if (w.getYmd() != null && key.equals(w.getYmd().trim())) { inV = w.getInqty(); outV = w.getOutqty(); inC = w.getCube_inqty(); outC = w.getCube_outqty(); inC = w.getCubeInqty(); outC = w.getCubeOutqty(); break; } } @@ -863,18 +877,21 @@ outqty.setData(data2.toArray(array2)); list.add(outqty); AxisBean cubeInqty = new AxisBean(); cubeInqty.setName("入库体积"); Integer[] array3 = new Integer[data3.size()]; cubeInqty.setData(data3.toArray(array3)); list.add(cubeInqty); if (data3.size() >0) { AxisBean cubeInqty = new AxisBean(); cubeInqty.setName("入库体积"); Integer[] array3 = new Integer[data3.size()]; cubeInqty.setData(data3.toArray(array3)); list.add(cubeInqty); } AxisBean cubeOutqty = new AxisBean(); cubeOutqty.setName("出库体积"); Integer[] array4 = new Integer[data4.size()]; cubeOutqty.setData(data4.toArray(array4)); list.add(cubeOutqty); if (data3.size() >0) { AxisBean cubeOutqty = new AxisBean(); cubeOutqty.setName("出库体积"); Integer[] array4 = new Integer[data4.size()]; cubeOutqty.setData(data4.toArray(array4)); list.add(cubeOutqty); } map.put("categories", categories); map.put("rows", list); return R.ok(map); src/main/java/com/zy/asrs/entity/WorkChartAxis.java
@@ -12,6 +12,6 @@ private String ymd; private int inqty; private int outqty; private double cube_inqty; private double cube_outqty; private double cubeInqty; private double cubeOutqty; } src/main/java/com/zy/asrs/mapper/ReportQueryMapper.java
@@ -72,7 +72,7 @@ /** * 按小时汇总(视图 v_asr_inout_hourly_stat,ymd 与接口 yyyy-MM-dd HH 对齐) */ @Select("SELECT ymd, inqty, outqty,cube_inqty,cube_outqty FROM v_asr_inout_hourly_stat ORDER BY ymd") @Select("SELECT ymd, inqty, outqty,cube_inqty as cubeInqty,cube_outqty as cubeOutqty FROM v_asr_inout_hourly_stat ") List<WorkChartAxis> getChartAxisHourly(); /** src/main/java/com/zy/asrs/service/OpenService.java
@@ -6,6 +6,7 @@ import com.zy.asrs.entity.result.OpenOrderCompeteResult; import com.zy.asrs.entity.result.StockVo; import java.util.Map; import java.util.List; public interface OpenService { @@ -85,12 +86,12 @@ /** * 7.11 出库通知单(传递有序无序规则) */ R outOrder(OutTaskParam param,int count); R outOrder(OutTaskParam param,int count,int i); /** * 7.11 出库通知单(传递有序无序规则)批量建单,同一事务:任一行失败则全部回滚。 */ R outOrderBatch(List<OutTaskParam> params); R outOrderBatch(Map<String, List<OutTaskParam>> linesByBatchSeq); /** * 7.9 出库异常变动上报 src/main/java/com/zy/asrs/service/impl/ExternalTaskFacadeServiceImpl.java
@@ -87,10 +87,10 @@ } if (param.getSeq() == null) { param.setSeq(1); param.setSeq(0); } R result = openService.outOrder(param, 1); R result = openService.outOrder(param, 1,1); if (!Objects.equals(result.get("code"), 200) || !autoConfirm) { return result; } src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java
@@ -1504,11 +1504,11 @@ * 7.11 出库通知单(传递有序无序规则)单条建单。 */ @Override public R outOrder(OutTaskParam param,int count) { return outOrder(param, count, 0); public R outOrder(OutTaskParam param,int count,int i) { return outOrder(param, count, 0 ,i); } private R outOrder(OutTaskParam param, int count, int teu) { private R outOrder(OutTaskParam param, int count, int teu , int i) { LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>().eq("loc_sts", "F").eq("barcode", param.getPalletId())); if (locMast == null) { throw new CoolException("没有找到托盘码=" + param.getPalletId() + "对应的库位"); @@ -1599,7 +1599,7 @@ // 7.11:entryWmsCode、outDoorNo 复用明细备用字段。 wrkDetl.setStandby1(param.getEntryWmsCode()); wrkDetl.setStandby2(param.getOutDoorNo()); wrkDetl.setSupp(param.getSeq()+"/"+count); wrkDetl.setSupp(i+"/"+count); wrkDetl.setTeu(param.getTeu()); if (!wrkDetlService.insert(wrkDetl)) { @@ -1627,20 +1627,23 @@ */ @Override @Transactional(rollbackFor = Exception.class) public R outOrderBatch(List<OutTaskParam> params) { int n = params.size(); Map<String, Integer> batchLineCounts = new HashMap<>(); Map<String, Integer> batchTeuCounts = buildOutOrderBatchTeuCounts(params); for (OutTaskParam outTaskParam : params) { batchLineCounts.merge(buildOutOrderBatchKey(outTaskParam), 1, Integer::sum); } for (OutTaskParam outTaskParam : params) { int count = batchLineCounts.getOrDefault(buildOutOrderBatchKey(outTaskParam), n); int teu = batchTeuCounts.getOrDefault(outTaskParam.getBatchSeq(), 1); R r = outOrder(outTaskParam, count, teu); if (!Objects.equals(r.get("code"), 200)) { throw new CoolException("出库建单失败"); public R outOrderBatch(Map<String, List<OutTaskParam>> linesByBatchSeq) { for (Map.Entry<String, List<OutTaskParam>> entry : linesByBatchSeq.entrySet()) { int i = 0; for (OutTaskParam outTaskParam : entry.getValue()) { if(outTaskParam.getSeq()!=0){ i= outTaskParam.getSeq(); }else{ i++; } int count = entry.getValue().size(); int teu = outTaskParam.getTeu(); R r = outOrder(outTaskParam, count, teu ,i); if (!Objects.equals(r.get("code"), 200)) { throw new CoolException("出库建单失败"); } } } return R.ok(); } src/main/java/com/zy/asrs/task/WorkMastScheduler.java
@@ -16,10 +16,14 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Date; import java.util.Map; import java.util.List; import java.util.Objects; /** * Created by vincent on 2020/7/7 @@ -59,11 +63,11 @@ * 任务自动下发。 * <p> * 调度器只负责从工作档中挑出“当前允许下发”的任务,并将其转换成 WCS 接口需要的报文结构; * 真正的批量分组、调用 WCS、以及下发成功后的状态推进都放在 service 层统一处理。 * 出库任务按 userNo -> batchSeq 分层汇总后串行下发,确保同一 userNo 下前一个 batchSeq 完成后再发下一个。 * <p> * 当前批量下发的归并维度是: * 1. WCS接口路径(入库/出库/移库不能混发); * 2. work_mast.user_no + batch_seq(相同订单同批次的任务必须放到同一批次一起上报)。 * 2. 出库任务按 work_mast.user_no -> work_mast.batch_seq 分层汇总,并按 batchSeq 自然升序下发。 * * @author Ryan * @date 2026/1/10 14:42 @@ -80,56 +84,158 @@ } List<WorkTaskParams> paramsList = new ArrayList<>(); Map<String, LinkedHashMap<String, List<WorkTaskParams>>> outboundTasksByUserNo = new LinkedHashMap<>(); for (WrkMast wrkMast : wrkMasts) { // 出库类任务(ioType > 100)默认需要 ERP 确认;未确认的任务在这里直接跳过。 if (wrkMast.getIoType() > 100 && !"Y".equalsIgnoreCase(wrkMast.getPdcType())) { continue; } // WMS 库位编码转换成 WCS 可识别的库位编码。 String wcsSourceLocNo = Cools.isEmpty(wrkMast.getSourceLocNo()) ? "" : Utils.WMSLocToWCSLoc(wrkMast.getSourceLocNo()); String wcsLocNo = Cools.isEmpty(wrkMast.getLocNo()) ? "" : Utils.WMSLocToWCSLoc(wrkMast.getLocNo()); WorkTaskParams params = new WorkTaskParams(); // 101: 出库。有序任务才向 WCS 传 batch/batchSeq;seq=0 表示无序,不传这两个字段。 if(wrkMast.getIoType()==101) { params.setType("out") .setTaskNo(wrkMast.getWrkNo()+"") .setLocNo(wcsSourceLocNo) .setStaNo(String.valueOf(wrkMast.getStaNo())) .setTaskPri(wrkMast.getIoPri().intValue()) .setBarcode(wrkMast.getBarcode()); if (wrkMast.getPltType() != null && wrkMast.getPltType() > 0) { params.setBatch(wrkMast.getUserNo()) .setBatchSeq(wrkMast.getPltType()); } // 2: 入库。入库接口使用 sourceStaNo + 目标库位。 } else if (wrkMast.getIoType() == 2 && !Cools.isEmpty(wrkMast.getSourceStaNo())) { params.setType("in") .setTaskNo(wrkMast.getWrkNo()+"") .setSourceStaNo(String.valueOf(wrkMast.getSourceStaNo())) .setLocNo(wcsLocNo) .setTaskPri(wrkMast.getIoPri().intValue()) .setBarcode(wrkMast.getBarcode()); // 其余走移库接口,源库位和目标库位都需要带给 WCS。 WorkTaskParams params = buildWorkTaskParams(wrkMast); if (isOutboundPublishTask(wrkMast)) { String userNo = normalizeGroupKey(wrkMast.getUserNo()); String batchSeq = normalizeGroupKey(wrkMast.getBatchSeq()); outboundTasksByUserNo .computeIfAbsent(userNo, key -> new LinkedHashMap<>()) .computeIfAbsent(batchSeq, key -> new ArrayList<>()) .add(params); } else { params.setType("move") .setTaskNo(wrkMast.getWrkNo()+"") .setSourceLocNo(wcsSourceLocNo) .setLocNo(wcsLocNo) .setBarcode(wrkMast.getBarcode()); paramsList.add(params); } paramsList.add(params); } if (paramsList.isEmpty()) { if (!paramsList.isEmpty()) { R r = wcsApiService.pubWrksToWcs(paramsList); if (r == null || !Objects.equals(r.get("code"), 200)) { log.warn("批量下发任务到WCS失败, result={}", r); } } if (outboundTasksByUserNo.isEmpty()) { return; } // service 层会继续按“接口路径 + userNo”分组后再批量上报。 R r = wcsApiService.pubWrksToWcs(paramsList); if (!r.get("code").equals(200)) { log.warn("批量下发任务到WCS失败, result={}", r); for (Map.Entry<String, LinkedHashMap<String, List<WorkTaskParams>>> userEntry : outboundTasksByUserNo.entrySet()) { String userNo = userEntry.getKey(); List<String> batchSeqs = new ArrayList<>(userEntry.getValue().keySet()); batchSeqs.sort(this::compareBatchSeqNatural); for (String batchSeq : batchSeqs) { String blockingBatchSeq = findFirstUnfinishedOutboundBatchSeq(userNo); if (!Objects.equals(batchSeq, blockingBatchSeq)) { log.info("出库批次未完成,暂停后续下发, userNo={}, blockingBatchSeq={}, nextBatchSeq={}", userNo, blockingBatchSeq, batchSeq); break; } List<WorkTaskParams> batchParams = userEntry.getValue().get(batchSeq); if (batchParams == null || batchParams.isEmpty()) { continue; } R r = wcsApiService.pubWrksToWcs(batchParams); if (r == null || !Objects.equals(r.get("code"), 200)) { log.warn("批量下发出库任务到WCS失败, userNo={}, batchSeq={}, result={}", userNo, batchSeq, r); break; } } } } private WorkTaskParams buildWorkTaskParams(WrkMast wrkMast) { // WMS 库位编码转换成 WCS 可识别的库位编码。 String wcsSourceLocNo = Cools.isEmpty(wrkMast.getSourceLocNo()) ? "" : Utils.WMSLocToWCSLoc(wrkMast.getSourceLocNo()); String wcsLocNo = Cools.isEmpty(wrkMast.getLocNo()) ? "" : Utils.WMSLocToWCSLoc(wrkMast.getLocNo()); WorkTaskParams params = new WorkTaskParams(); // 101: 出库。有序任务才向 WCS 传 batch/batchSeq;seq=0 表示无序,不传这两个字段。 if (wrkMast.getIoType() == 101) { params.setType("out") .setTaskNo(wrkMast.getWrkNo() + "") .setLocNo(wcsSourceLocNo) .setStaNo(String.valueOf(wrkMast.getStaNo())) .setTaskPri(wrkMast.getIoPri().intValue()) .setBarcode(wrkMast.getBarcode()); if (wrkMast.getPltType() != null && wrkMast.getPltType() > 0) { params.setBatch(wrkMast.getUserNo()) .setBatchSeq(wrkMast.getPltType()); } // 2: 入库。入库接口使用 sourceStaNo + 目标库位。 } else if (wrkMast.getIoType() == 2 && !Cools.isEmpty(wrkMast.getSourceStaNo())) { params.setType("in") .setTaskNo(wrkMast.getWrkNo() + "") .setSourceStaNo(String.valueOf(wrkMast.getSourceStaNo())) .setLocNo(wcsLocNo) .setTaskPri(wrkMast.getIoPri().intValue()) .setBarcode(wrkMast.getBarcode()); // 其余走移库接口,源库位和目标库位都需要带给 WCS。 } else { params.setType("move") .setTaskNo(wrkMast.getWrkNo() + "") .setSourceLocNo(wcsSourceLocNo) .setLocNo(wcsLocNo) .setBarcode(wrkMast.getBarcode()); } return params; } private boolean isOutboundPublishTask(WrkMast wrkMast) { return wrkMast != null && Objects.equals(wrkMast.getIoType(), 101); } private String findFirstUnfinishedOutboundBatchSeq(String userNo) { EntityWrapper<WrkMast> wrapper = new EntityWrapper<>(); if (Cools.isEmpty(userNo)) { wrapper.isNull("user_no"); } else { wrapper.eq("user_no", userNo); } wrapper.eq("io_type", 101); wrapper.lt("wrk_sts", 14); List<WrkMast> rows = wrkMastService.selectList(wrapper); if (rows == null || rows.isEmpty()) { return null; } String firstBatchSeq = null; for (WrkMast row : rows) { String batchSeq = normalizeGroupKey(row.getBatchSeq()); if (firstBatchSeq == null || compareBatchSeqNatural(batchSeq, firstBatchSeq) < 0) { firstBatchSeq = batchSeq; } } return firstBatchSeq; } private int compareBatchSeqNatural(String left, String right) { String safeLeft = Cools.isEmpty(left) ? "" : left; String safeRight = Cools.isEmpty(right) ? "" : right; boolean leftNumeric = isDigits(safeLeft); boolean rightNumeric = isDigits(safeRight); if (leftNumeric && rightNumeric) { BigInteger leftValue = new BigInteger(safeLeft); BigInteger rightValue = new BigInteger(safeRight); int compare = leftValue.compareTo(rightValue); if (compare != 0) { return compare; } } return safeLeft.compareTo(safeRight); } private boolean isDigits(String value) { if (Cools.isEmpty(value)) { return false; } for (int i = 0; i < value.length(); i++) { if (!Character.isDigit(value.charAt(i))) { return false; } } return true; } private String normalizeGroupKey(String value) { return Cools.isEmpty(value) ? "" : value; } } src/main/resources/application.yml
@@ -93,7 +93,7 @@ ErpReportOld: true # 地址 address: URL: http://192.168.160.8:8088 URL: http://192.168.100.148:62482 #入库上报 Inaddress: /api/Service/InPalletCompleted #出库上报