1.针对7.3接口文档,新增了几个字段需要加入到组托档中,不额外加字段
2.针对7.11接口文档,对outOrder方法进行重写,batchSeq在wrkMast表中新增一个字段放,entryWmsCode、outDoorNo这两个在wrkDetl中找两个字段存放
3.针对7.7接口文档,上报时加上orderId出库单号
4.针对7.9接口文档,wcs会先请求wms,只需要palletId托盘码,errorMsg错误信息;wms转发给加上orderId出库单号转发给ERP
5.针对7.10接口文档,ERP先按照这个文档发给wms,wms再发给wcs,发给wcs这块先不写
| | |
| | | private static final Long WCS_SYNC_USER = 9999L; |
| | | private static final String YES = "Y"; |
| | | private static final String NO = "N"; |
| | | private static final long OUT_LOCK_REPORT_PENDING_WRK_STS = 13L; |
| | | private static final long OUT_LOCK_REPORT_SUCCESS_WRK_STS = 21L; |
| | | private static final long OUT_LOCK_REPORT_FAIL_WRK_STS = 22L; |
| | | private static final String OUT_LOCK_REPORT_PENDING_FLAG = "P"; |
| | | |
| | | /** 同一 WCS 路径、同一单号下一组下发的任务条数上限 */ |
| | | private static final int WCS_PUB_BATCH_SIZE = 20; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 出库:仅当单号、序号均有效时做跳号校验;单号空或序号无效仍下发。入库/移库不处理。 |
| | | * 出库:仅当单号、批次、序号均有效时做批次内跳号校验;无效时仍下发。入库/移库不处理。 |
| | | */ |
| | | private List<WorkTaskParams> filterOutboundByContiguousPlt(List<WorkTaskParams> accepted, Map<String, WrkMast> wrkMastMap, List<String> skipMsgs) { |
| | | Map<String, Integer> reachCache = new HashMap<>(); |
| | |
| | | } |
| | | WrkMast w = wrkMastMap.get(p.getTaskNo()); |
| | | String userNo = sortUserNoForPub(p, w); |
| | | String batchGroup = sortBatchGroupForPub(p, w); |
| | | Integer plt = sortPltForPub(p, w); |
| | | if (Cools.isEmpty(userNo) || plt == null || plt <= 0) { |
| | | kept.add(p); |
| | | continue; |
| | | } |
| | | int maxReach = reachCache.computeIfAbsent(userNo, wrkMastService::outboundSeqMaxContiguousPlt); |
| | | String cacheKey = buildOutboundBatchCacheKey(userNo, batchGroup); |
| | | int maxReach = reachCache.computeIfAbsent(cacheKey, key -> wrkMastService.outboundSeqMaxContiguousPlt(userNo, batchGroup)); |
| | | if (plt > maxReach) { |
| | | skipMsgs.add(buildTaskMsg(p, "出库序号跳号,跳过")); |
| | | continue; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 同单下一组:优先 WCS queryTask;失败或无数据则主表已非 11 或已进历史表。 |
| | | * 同单同批下一组:优先 WCS queryTask;失败或无数据则主表已非 11 或已进历史表。 |
| | | */ |
| | | private boolean sameOrderNextChunkAllowed(List<WorkTaskParams> lastSentChunk) { |
| | | if (lastSentChunk == null || lastSentChunk.isEmpty()) { |
| | |
| | | } |
| | | |
| | | /** |
| | | * 出库每组下发前:本组有有效最小序号且>1 时,只校验「最小序号-1」一档;序号全无则跳过本条件。 |
| | | * 出库每组下发前:本组有有效最小序号且>1 时,只校验「同单同批的最小序号-1」一档;序号全无则跳过本条件。 |
| | | */ |
| | | private boolean outboundChunkPredecessorPltReady(List<WorkTaskParams> chunk, Map<String, WrkMast> wrkMastMap) { |
| | | if (chunk == null || chunk.isEmpty()) { |
| | |
| | | } |
| | | WrkMast headMast = wrkMastMap.get(head.getTaskNo()); |
| | | String userNo = sortUserNoForPub(head, headMast); |
| | | String batchGroup = sortBatchGroupForPub(head, headMast); |
| | | if (Cools.isEmpty(userNo)) { |
| | | return true; |
| | | } |
| | |
| | | if (minPlt == Integer.MAX_VALUE || minPlt <= 1) { |
| | | return true; |
| | | } |
| | | return outboundPltSlotReleasedInWms(userNo, minPlt - 1); |
| | | return outboundPltSlotReleasedInWms(userNo, batchGroup, minPlt - 1); |
| | | } |
| | | |
| | | private boolean outboundPltSlotReleasedInWms(String userNo, int pltType) { |
| | | List<WrkMast> rows = wrkMastService.selectList(new EntityWrapper<WrkMast>() |
| | | .eq("user_no", userNo) |
| | | .eq("io_type", 101) |
| | | .eq("plt_type", pltType)); |
| | | private boolean outboundPltSlotReleasedInWms(String userNo, String batchSeq, int pltType) { |
| | | EntityWrapper<WrkMast> mastWrapper = new EntityWrapper<>(); |
| | | mastWrapper.eq("user_no", userNo); |
| | | mastWrapper.eq("io_type", 101); |
| | | mastWrapper.eq("plt_type", pltType); |
| | | if (batchSeq == null) { |
| | | mastWrapper.isNull("batch_seq"); |
| | | } else { |
| | | mastWrapper.eq("batch_seq", batchSeq); |
| | | } |
| | | List<WrkMast> rows = wrkMastService.selectList(mastWrapper); |
| | | if (rows != null && !rows.isEmpty()) { |
| | | for (WrkMast m : rows) { |
| | | if (m != null && m.getWrkSts() != null && Objects.equals(m.getWrkSts(), 11L)) { |
| | |
| | | } |
| | | return true; |
| | | } |
| | | int logCnt = wrkMastLogService.selectCount(new EntityWrapper<WrkMastLog>() |
| | | .eq("user_no", userNo) |
| | | .eq("io_type", 101) |
| | | .eq("plt_type", pltType)); |
| | | EntityWrapper<WrkMastLog> logWrapper = new EntityWrapper<>(); |
| | | logWrapper.eq("user_no", userNo); |
| | | logWrapper.eq("io_type", 101); |
| | | logWrapper.eq("plt_type", pltType); |
| | | if (batchSeq == null) { |
| | | logWrapper.isNull("batch_seq"); |
| | | } else { |
| | | logWrapper.eq("batch_seq", batchSeq); |
| | | } |
| | | int logCnt = wrkMastLogService.selectCount(logWrapper); |
| | | return logCnt > 0; |
| | | } |
| | | |
| | |
| | | return Comparator |
| | | .comparing((WorkTaskParams p) -> Optional.ofNullable(p.getType()).orElse(""), String.CASE_INSENSITIVE_ORDER) |
| | | .thenComparing(p -> sortUserNoForPub(p, wrkMastMap.get(p.getTaskNo())), Comparator.nullsLast(String::compareTo)) |
| | | .thenComparing(p -> sortBatchGroupForPub(p, wrkMastMap.get(p.getTaskNo())), Comparator.nullsLast(String::compareTo)) |
| | | .thenComparing(p -> sortPltForPub(p, wrkMastMap.get(p.getTaskNo())), Comparator.nullsLast(Integer::compareTo)); |
| | | } |
| | | |
| | |
| | | return wrkMast.getPltType(); |
| | | } |
| | | return p.getBatchSeq(); |
| | | } |
| | | |
| | | private static String sortBatchGroupForPub(WorkTaskParams p, WrkMast wrkMast) { |
| | | if (wrkMast != null) { |
| | | return wrkMast.getBatchSeq(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private static String buildOutboundBatchCacheKey(String userNo, String batchSeq) { |
| | | String safeUserNo = Cools.isEmpty(userNo) ? "_NO_USER_" : userNo; |
| | | String safeBatchSeq = Cools.isEmpty(batchSeq) ? "_NO_BATCH_" : batchSeq; |
| | | return safeUserNo + "#" + safeBatchSeq; |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | |
| | | if (params.getNotifyType().equals("task")) { |
| | | if (isOutboundCrnTaskRun(params)) { |
| | | // WCS出库任务开始:堆垛机开始执行出库任务,工作状态 12 -> 13。 |
| | | if (isOutboundTask(mast) && Objects.equals(mast.getWrkSts(), 12L)) { |
| | | mast.setWrkSts(OUT_LOCK_REPORT_PENDING_WRK_STS); |
| | | mast.setExpTime(0D); |
| | | mast.setLogMk(OUT_LOCK_REPORT_PENDING_FLAG); |
| | | mast.setLogErrMemo(null); |
| | | mast.setLogErrTime(null); |
| | | mast.setModiTime(new Date()); |
| | | if (!wrkMastService.updateById(mast)) { |
| | | throw new CoolException("任务状态修改失败!!"); |
| | | } |
| | | } |
| | | } else if (params.getNotifyType().equals("task")) { |
| | | //任务 |
| | | if (params.getMsgType().equals("task_complete")) { |
| | | |
| | | if (mast.getIoType() == 1 || mast.getIoType() == 2 ||mast.getIoType() == 10) { |
| | | mast.setWrkSts(4L); |
| | | } else if ((mast.getIoType() == 101||mast.getIoType()==110) && mast.getWrkSts()<14) { |
| | | } else if (isOutboundTask(mast) && canMarkOutboundTaskComplete(mast)) { |
| | | mast.setWrkSts(14L); |
| | | if(Cools.isEmpty(mast.getStaNo())){ |
| | | mast.setOveMk("Y"); |
| | |
| | | |
| | | } |
| | | return R.ok(); |
| | | } |
| | | |
| | | private boolean isOutboundCrnTaskRun(ReceviceTaskParams params) { |
| | | return params != null |
| | | && "Crn".equalsIgnoreCase(params.getNotifyType()) |
| | | && "crn_out_task_run".equalsIgnoreCase(params.getMsgType()); |
| | | } |
| | | |
| | | private boolean isOutboundTask(WrkMast mast) { |
| | | return mast != null && mast.getIoType() != null && (mast.getIoType() == 101 || mast.getIoType() == 110); |
| | | } |
| | | |
| | | private boolean canMarkOutboundTaskComplete(WrkMast mast) { |
| | | if (mast == null || mast.getWrkSts() == null) { |
| | | return false; |
| | | } |
| | | return mast.getWrkSts() < 14 |
| | | || mast.getWrkSts().equals(OUT_LOCK_REPORT_SUCCESS_WRK_STS) |
| | | || mast.getWrkSts().equals(OUT_LOCK_REPORT_FAIL_WRK_STS); |
| | | } |
| | | |
| | | @Override |
| | |
| | | * <p> |
| | | * 分组规则: |
| | | * 1. 先按接口路径区分,避免不同任务类型混用同一个 WCS 接口; |
| | | * 2. 再按 userNo 区分,确保相同 userNo 的任务一起上报。 |
| | | * 2. 再按 userNo + batchSeq 区分,确保相同订单同批次的任务一起上报。 |
| | | * <p> |
| | | * 正常情况下 userNo 取自 work_mast.user_no; |
| | | * 如果当前没查到工作档,则回退到请求里的 batch 字段,保证兼容已有调用。 |
| | | * batchSeq 取自 work_mast.batch_seq;如果当前没查到工作档,则只按 userNo 回退兼容已有调用。 |
| | | */ |
| | | private String buildBatchGroupKey(WorkTaskParams params, WrkMast wrkMast) { |
| | | String path = resolveTaskPath(params); |
| | | String userNo = wrkMast == null ? null : wrkMast.getUserNo(); |
| | | String batchGroup = wrkMast == null ? null : wrkMast.getBatchSeq(); |
| | | if (Cools.isEmpty(userNo)) { |
| | | userNo = params.getBatch(); |
| | | } |
| | | if (Cools.isEmpty(userNo)) { |
| | | userNo = "_NO_USER_"; |
| | | } |
| | | return path + "#" + userNo; |
| | | String batchKey = Cools.isEmpty(batchGroup) ? "_NO_BATCH_" : batchGroup; |
| | | return path + "#" + userNo + "#" + batchKey; |
| | | } |
| | | |
| | | /** |
| | |
| | | if (!Cools.isEmpty(params.getStaNo())) { |
| | | task.put("staNo", params.getStaNo()); |
| | | } |
| | | if (!Cools.isEmpty(params.getBatch())) { |
| | | boolean includeOutBatch = !"out".equalsIgnoreCase(params.getType()) |
| | | || (params.getBatchSeq() != null && params.getBatchSeq() > 0); |
| | | if (includeOutBatch && !Cools.isEmpty(params.getBatch())) { |
| | | task.put("batch", params.getBatch()); |
| | | } |
| | | if (!Objects.isNull(params.getBatchSeq())) { |
| | | if (includeOutBatch && !Objects.isNull(params.getBatchSeq())) { |
| | | task.put("batchSeq", params.getBatchSeq()); |
| | | } |
| | | return task; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 组托信息下发 |
| | | * return |
| | | * 7.3 组托信息下发 |
| | | */ |
| | | @PostMapping("/comb/auth") |
| | | public synchronized R comb(@RequestBody ArrayList<MesToCombParam> param, HttpServletRequest request) { |
| | |
| | | } |
| | | |
| | | /** |
| | | * 出库通知单 |
| | | * 7.11 出库通知单(传递有序无序规则) |
| | | */ |
| | | |
| | | @PostMapping("/outOrder") |
| | | public synchronized R outOrder(@RequestBody ArrayList<OutTaskParam> params, HttpServletRequest request) { |
| | | if (Cools.isEmpty(params)) { |
| | |
| | | } |
| | | log.info("[outOrder] cache: {}", JSON.toJSONString(params)); |
| | | request.setAttribute("cache", params); |
| | | Set<String> orderIds = new LinkedHashSet<>(); |
| | | Map<String, List<OutTaskParam>> linesByBatch = new LinkedHashMap<>(); |
| | | for (OutTaskParam outTaskParam : params) { |
| | | if (Cools.isEmpty(outTaskParam) || Cools.isEmpty(outTaskParam.getOrderId())) { |
| | | return R.error("出库单号不能为空"); |
| | | } |
| | | orderIds.add(outTaskParam.getOrderId()); |
| | | if (Cools.isEmpty(outTaskParam.getBatchSeq())) { |
| | | outTaskParam.setBatchSeq(outTaskParam.getOrderId()); |
| | | } |
| | | if (Cools.isEmpty(outTaskParam.getStationId())) { |
| | | return R.error("托盘「" + outTaskParam.getPalletId() + "」出库口编码不能为空"); |
| | | } |
| | | linesByBatch.computeIfAbsent(outTaskParam.getBatchSeq(), k -> new ArrayList<>()).add(outTaskParam); |
| | | } |
| | | |
| | | Map<String, List<OutTaskParam>> linesByOrder = new LinkedHashMap<>(); |
| | | for (OutTaskParam outTaskParam : params) { |
| | | linesByOrder.computeIfAbsent(outTaskParam.getOrderId(), k -> new ArrayList<>()).add(outTaskParam); |
| | | } |
| | | for (Map.Entry<String, List<OutTaskParam>> entry : linesByOrder.entrySet()) { |
| | | String oid = entry.getKey(); |
| | | for (Map.Entry<String, List<OutTaskParam>> entry : linesByBatch.entrySet()) { |
| | | List<OutTaskParam> lines = entry.getValue(); |
| | | List<Integer> seqs = new ArrayList<>(lines.size()); |
| | | OutTaskParam head = lines.get(0); |
| | | String oid = head.getOrderId(); |
| | | String batchSeq = head.getBatchSeq(); |
| | | boolean hasZero = false; |
| | | boolean hasPositive = false; |
| | | List<Integer> orderedSeqs = new ArrayList<>(lines.size()); |
| | | for (OutTaskParam line : lines) { |
| | | if (line.getSeq() == null) { |
| | | return R.error("出库单「" + oid + "」序号不能为空"); |
| | | return R.error("出库单「" + oid + "」批次「" + batchSeq + "」序号不能为空"); |
| | | } |
| | | seqs.add(line.getSeq()); |
| | | if (line.getSeq() < 0) { |
| | | return R.error("出库单「" + oid + "」批次「" + batchSeq + "」序号不能小于0"); |
| | | } |
| | | if (line.getSeq() == 0) { |
| | | hasZero = true; |
| | | } else { |
| | | hasPositive = true; |
| | | orderedSeqs.add(line.getSeq()); |
| | | } |
| | | } |
| | | Collections.sort(seqs); |
| | | for (int i = 0; i < seqs.size(); i++) { |
| | | if (!String.valueOf(seqs.get(i)).equals(String.valueOf(i + 1))) { |
| | | return R.error("出库单「" + oid + "」序号不连贯"); |
| | | if (hasZero && hasPositive) { |
| | | return R.error("出库单「" + oid + "」批次「" + batchSeq + "」序号不能混用无序和有序"); |
| | | } |
| | | if (!hasZero) { |
| | | Collections.sort(orderedSeqs); |
| | | for (int i = 0; i < orderedSeqs.size(); i++) { |
| | | if (!Objects.equals(orderedSeqs.get(i), i + 1)) { |
| | | return R.error("出库单「" + oid + "」批次「" + batchSeq + "」序号不连贯"); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * 7.9 出库异常变动上报 |
| | | */ |
| | | @PostMapping("/order/pakout/abnormal/report/v1") |
| | | public synchronized R outOrderAbnormalReport(@RequestBody OutOrderAbnormalReportParam param, HttpServletRequest request) { |
| | | if (request != null) { |
| | | log.info("[outOrderAbnormalReport] cache: {}", param == null ? "null" : JSON.toJSONString(param)); |
| | | request.setAttribute("cache", param); |
| | | } |
| | | if (Cools.isEmpty(param) || Cools.isEmpty(param.getPalletId())) { |
| | | return R.error("palletId不能为空"); |
| | | } |
| | | return openService.outOrderAbnormalReport(param); |
| | | } |
| | | |
| | | /** |
| | | * 7.10 出库异常变动处理 |
| | | */ |
| | | @PostMapping("/order/pakout/abnormal/handle/v1") |
| | | public synchronized R outOrderAbnormalHandle(@RequestBody OutOrderAbnormalHandleParam param, HttpServletRequest request) { |
| | | if (request != null) { |
| | | log.info("[outOrderAbnormalHandle] cache: {}", param == null ? "null" : JSON.toJSONString(param)); |
| | | request.setAttribute("cache", param); |
| | | } |
| | | if (Cools.isEmpty(param) || Cools.isEmpty(param.getPalletId())) { |
| | | return R.error("palletId不能为空"); |
| | | } |
| | | return openService.outOrderAbnormalHandle(param); |
| | | } |
| | | |
| | | /** |
| | | * pause out order |
| | | */ |
| | | @PostMapping("/order/pakout/pause/default/v1") |
| | |
| | | return openService.pakoutOrderPause(param); |
| | | } |
| | | |
| | | private String buildOutOrderBatchKey(OutTaskParam param) { |
| | | return param.getOrderId() + "#" + param.getBatchSeq(); |
| | | } |
| | | |
| | | |
| | | /*************************************电视机程序***********************************************/ |
| | | |
| | |
| | | @TableField("plt_type") |
| | | private Integer pltType; |
| | | |
| | | @ApiModelProperty(value= "出库批次序号") |
| | | @TableField("batch_seq") |
| | | private String batchSeq; |
| | | |
| | | /** |
| | | * 拣料 |
| | | */ |
| | |
| | | @TableField("plt_type") |
| | | private Integer pltType; |
| | | |
| | | @ApiModelProperty(value= "") |
| | | @TableField("batch_seq") |
| | | private String batchSeq; |
| | | |
| | | /** |
| | | * 空板 |
| | | */ |
| New file |
| | |
| | | package com.zy.asrs.entity.param; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class ErpOutOrderAbnormalReportParam { |
| | | |
| | | // 托盘编码 |
| | | private String palletId; |
| | | |
| | | // 错误信息 |
| | | private String errorMsg; |
| | | |
| | | // 出库单号 |
| | | private String orderId; |
| | | } |
| New file |
| | |
| | | package com.zy.asrs.entity.param; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.util.Date; |
| | | |
| | | @Data |
| | | public class ErpOutTaskLockReportParam { |
| | | |
| | | // 托盘编码 |
| | | private String palletId; |
| | | |
| | | // 出库单号 |
| | | private String orderId; |
| | | |
| | | //开始时间 |
| | | private String startTime; |
| | | } |
| | |
| | | private Double anfme; |
| | | //进仓编号 |
| | | private String entryWmsCode; |
| | | //客户Id |
| | | private String customerId; |
| | | //客户名称 |
| | | private String customerName; |
| | | //item编号 |
| | | private String item; |
| | | //po单号 |
| | | private String orderId; |
| | | //批号 |
| | |
| | | private String bizNo; |
| | | //是否散货,0 非散货;1 散货;为了管控出货速率,散货可以出慢点。 |
| | | private Integer package1; |
| | | //外库门号 |
| | | private String outDoorNo; |
| | | //入库通知来源,ERP 默认 erp,MQTT 组托传 aws。 |
| | | private String boxType1; |
| | | } |
| New file |
| | |
| | | package com.zy.asrs.entity.param; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class OutOrderAbnormalHandleParam { |
| | | |
| | | // 托盘编码 |
| | | private String palletId; |
| | | |
| | | // 操作类型,0 转无序;2 调整至最后一托 |
| | | private Integer operateType; |
| | | } |
| New file |
| | |
| | | package com.zy.asrs.entity.param; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class OutOrderAbnormalReportParam { |
| | | |
| | | // 托盘编码 |
| | | private String palletId; |
| | | |
| | | // 错误信息 |
| | | private String errorMsg; |
| | | } |
| | |
| | | |
| | | @Data |
| | | public class OutTaskParam { |
| | | //同订单中批次标识 |
| | | private String batchSeq; |
| | | //出库顺序,从1开始 |
| | | private Integer seq; |
| | | //托盘编码 |
| | |
| | | private String entryWmsCode; |
| | | //出库单号 |
| | | private String orderId; |
| | | //外库门号 |
| | | private String outDoorNo; |
| | | } |
| | |
| | | @Repository |
| | | public interface WrkMastLogMapper extends BaseMapper<WrkMastLog> { |
| | | |
| | | @Insert("insert into asr_wrk_mast_log select * from asr_wrk_mast where wrk_no=#{workNo}") |
| | | @Insert("INSERT INTO asr_wrk_mast_log (" + |
| | | "wrk_no, inv_wh, ymd, mk, whs_type, wrk_sts, io_type, crn_no, sheet_no, io_pri, wrk_date, loc_no, sta_no, source_sta_no, source_loc_no, loc_sts, " + |
| | | "picking, link_mis, online_yn, upd_mk, exit_mk, plt_type, batch_seq, empty_mk, io_time, ctn_type, packed, ove_mk, mtn_type, user_no, crn_str_time, " + |
| | | "crn_end_time, plc_str_time, crn_pos_time, load_time, exp_time, ref_wrkno, ref_iotime, modi_user, modi_time, appe_user, appe_time, pause_mk, " + |
| | | "error_time, error_memo, ctn_kind, manu_type, memo_m, sc_weight, log_mk, log_err_time, log_err_memo, barcode, Pdc_type, ctn_no, full_plt, pre_have, take_none) " + |
| | | "SELECT " + |
| | | "wrk_no, inv_wh, ymd, mk, whs_type, wrk_sts, io_type, crn_no, sheet_no, io_pri, wrk_date, loc_no, sta_no, source_sta_no, source_loc_no, loc_sts, " + |
| | | "picking, link_mis, online_yn, upd_mk, exit_mk, plt_type, batch_seq, empty_mk, io_time, ctn_type, packed, ove_mk, mtn_type, user_no, crn_str_time, " + |
| | | "crn_end_time, plc_str_time, crn_pos_time, load_time, exp_time, ref_wrkno, ref_iotime, modi_user, modi_time, appe_user, appe_time, pause_mk, " + |
| | | "error_time, error_memo, ctn_kind, manu_type, memo, sc_weight, log_mk, log_err_time, log_err_memo, barcode, Pdc_type, ctn_no, full_plt, pre_have, take_none " + |
| | | "FROM asr_wrk_mast WHERE wrk_no=#{workNo}") |
| | | int save(Integer workNo); |
| | | |
| | | /** |
| | | * 出库 101 下该单号已出现的 plt_type(历史表) |
| | | * 出库 101 下该单号同批次已出现的 plt_type(历史表) |
| | | */ |
| | | @Select("SELECT plt_type FROM asr_wrk_mast_log WHERE io_type = 101 AND plt_type IS NOT NULL AND plt_type > 0 AND user_no = #{userNo}") |
| | | List<Integer> listOutboundPltTypesByUserNo(@Param("userNo") String userNo); |
| | | @Select("SELECT plt_type FROM asr_wrk_mast_log WHERE io_type = 101 AND plt_type IS NOT NULL AND plt_type > 0 AND user_no = #{userNo} AND ((#{batchSeq} IS NULL AND batch_seq IS NULL) OR batch_seq = #{batchSeq})") |
| | | List<Integer> listOutboundPltTypesByUserNo(@Param("userNo") String userNo, @Param("batchSeq") String batchSeq); |
| | | |
| | | /** |
| | | * 查询库存移动流水记录 |
| | |
| | | , @Param("boxType1")String boxType1, @Param("boxType2")String boxType2, @Param("boxType3")String boxType3, @Param("crnNo") Integer crnNo); |
| | | |
| | | /** |
| | | * 出库 101 下该单号已出现的 plt_type(主表) |
| | | * 出库 101 下该单号同批次已出现的 plt_type(主表) |
| | | */ |
| | | @Select("SELECT plt_type FROM asr_wrk_mast WHERE io_type = 101 AND plt_type IS NOT NULL AND plt_type > 0 AND user_no = #{userNo}") |
| | | List<Integer> listOutboundPltTypesByUserNo(@Param("userNo") String userNo); |
| | | @Select("SELECT plt_type FROM asr_wrk_mast WHERE io_type = 101 AND plt_type IS NOT NULL AND plt_type > 0 AND user_no = #{userNo} AND ((#{batchSeq} IS NULL AND batch_seq IS NULL) OR batch_seq = #{batchSeq})") |
| | | List<Integer> listOutboundPltTypesByUserNo(@Param("userNo") String userNo, @Param("batchSeq") String batchSeq); |
| | | |
| | | } |
| | |
| | | R stationAll (); |
| | | |
| | | /** |
| | | * mes发组托档给到wms |
| | | * 7.3 组托信息下发 |
| | | */ |
| | | R mesToComb(MesToCombParam param); |
| | | |
| | | /** |
| | | * |
| | | * 7.11 出库通知单(传递有序无序规则) |
| | | */ |
| | | R outOrder(OutTaskParam param,int count); |
| | | |
| | | /** |
| | | * 批量出库建单,同一事务:任一行失败则全部回滚。 |
| | | * 7.11 出库通知单(传递有序无序规则)批量建单,同一事务:任一行失败则全部回滚。 |
| | | */ |
| | | R outOrderBatch(List<OutTaskParam> params); |
| | | |
| | | /** |
| | | * 7.9 出库异常变动上报 |
| | | */ |
| | | R outOrderAbnormalReport(OutOrderAbnormalReportParam param); |
| | | |
| | | /** |
| | | * 7.10 出库异常变动处理 |
| | | */ |
| | | R outOrderAbnormalHandle(OutOrderAbnormalHandleParam param); |
| | | } |
| | | |
| | |
| | | WrkMast selectWrkMast(Integer workNo,String barcode); |
| | | |
| | | /** |
| | | * 出库(io_type=101)同一 user_no:主表与历史表并集下,自 1 起连续存在的最大 plt_type。 |
| | | * 出库(io_type=101)同一 user_no、同一 batch_seq:主表与历史表并集下,自 1 起连续存在的最大 plt_type。 |
| | | */ |
| | | int outboundSeqMaxContiguousPlt(String userNo); |
| | | int outboundSeqMaxContiguousPlt(String userNo, String batchSeq); |
| | | |
| | | } |
| | |
| | | private String mesUrl; |
| | | @Value("${mes.stationaddress}") |
| | | private String stationAddress; |
| | | @Value("${erp.address.URL:}") |
| | | private String erpUrl; |
| | | @Value("${erp.address.OutErroraddress:}") |
| | | private String erpOutErrorAddress; |
| | | @Autowired |
| | | private WaitPakinService waitPakinService; |
| | | @Autowired |
| | |
| | | return mesUrl + stationAddress; |
| | | } |
| | | |
| | | /** |
| | | * 7.3 组托信息下发。 |
| | | */ |
| | | @Override |
| | | public R mesToComb(MesToCombParam param) { |
| | | if (Cools.isEmpty(param.getPalletId())) { |
| | |
| | | waitPakin.setOrigin(String.valueOf(param.getStorageArea()));//建议入库区域 |
| | | waitPakin.setManu(String.valueOf(param.getLocId()));//mes具体库位编号 |
| | | waitPakin.setThreeCode(param.getBizNo()); |
| | | // 7.3 新增字段复用现有组托档承载位,不新增列。 |
| | | waitPakin.setSuppCode(param.getCustomerId()); |
| | | waitPakin.setSupp(param.getCustomerName()); |
| | | waitPakin.setStandby3(param.getItem()); |
| | | waitPakin.setStandby1(param.getEntryWmsCode()); |
| | | waitPakin.setStandby2(param.getOutDoorNo()); |
| | | waitPakin.setBeBatch(param.getPackage1());//是否散货,0 非散货;1 散货;为了管控出货速率,散货可以出慢点。 |
| | | // ERP 入口默认打 erp,MQTT 组托会在参数里显式传 aws。 |
| | | waitPakin.setBoxType1(Cools.isEmpty(param.getBoxType1()) ? "erp" : param.getBoxType1()); |
| | |
| | | return R.ok().add(Cools.add("palletId", param.getPalletId()).add("orderId", param.getOrderId())); |
| | | } |
| | | |
| | | /** |
| | | * 7.11 出库通知单(传递有序无序规则)单条建单。 |
| | | */ |
| | | @Override |
| | | public R outOrder(OutTaskParam param,int count) { |
| | | LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>().eq("loc_sts", "F").eq("barcode", param.getPalletId())); |
| | | if (locMast == null) { |
| | | throw new CoolException("没有找到托盘码=" + param.getPalletId() + "对应的库位"); |
| | | } |
| | | if (Cools.isEmpty(param.getStationId())) { |
| | | throw new CoolException("出库口编码不能为空"); |
| | | } |
| | | if (Cools.isEmpty(param.getBatchSeq())) { |
| | | throw new CoolException("批次标识不能为空"); |
| | | } |
| | | if (param.getSeq() == null || param.getSeq() < 0) { |
| | | throw new CoolException("出库顺序不能为空且不能小于0"); |
| | | } |
| | | Integer ioType = 101; |
| | | // 获取路径 |
| | | StaDesc staDesc = staDescService.queryCrnStn(ioType, locMast.getCrnNo(), Integer.valueOf(param.getStationId())); |
| | | Integer stationId; |
| | | try { |
| | | stationId = Integer.valueOf(param.getStationId()); |
| | | } catch (NumberFormatException ex) { |
| | | throw new CoolException("出库口编码格式错误:" + param.getStationId()); |
| | | } |
| | | StaDesc staDesc = staDescService.queryCrnStn(ioType, locMast.getCrnNo(), stationId); |
| | | if (staDesc == null) { |
| | | throw new CoolException("未找到出库口=" + param.getStationId() + "对应路径"); |
| | | } |
| | | Date now = new Date(); |
| | | // 生成工作号 |
| | | int workNo = commonService.getWorkNo(WorkNoType.getWorkNoType(ioType)); |
| | |
| | | wrkMast.setEmptyMk("N"); // 空板 |
| | | wrkMast.setLinkMis("N"); |
| | | wrkMast.setPdcType("N"); |
| | | // 7.11:orderId 存 userNo,batchSeq 存批次标识,seq 存批次内顺序。 |
| | | wrkMast.setUserNo(param.getOrderId());//订单号 |
| | | wrkMast.setBatchSeq(param.getBatchSeq());//订单内批次标识 |
| | | wrkMast.setPltType(param.getSeq());//出库顺序,从1开始 |
| | | wrkMast.setTakeNone("0"); //0非自动 |
| | | wrkMast.setAppeUser(9995L); // 操作人员数据 |
| | |
| | | wrkDetl.setAppeUser(9995L); |
| | | wrkDetl.setModiTime(now); |
| | | wrkDetl.setModiUser(9995L); |
| | | // 7.11:entryWmsCode、outDoorNo 复用明细备用字段。 |
| | | wrkDetl.setStandby1(param.getEntryWmsCode()); |
| | | wrkDetl.setStandby2(param.getOutDoorNo()); |
| | | wrkDetl.setSupp(param.getSeq()+"/"+count); |
| | | |
| | | if (!wrkDetlService.insert(wrkDetl)) { |
| | |
| | | return R.ok().add(Cools.add("wrkNo", workNo).add("orderId", param.getOrderId())); |
| | | } |
| | | |
| | | /** |
| | | * 7.11 出库通知单(传递有序无序规则)批量建单。 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public R outOrderBatch(List<OutTaskParam> params) { |
| | | int n = params.size(); |
| | | Map<String, Integer> batchLineCounts = new HashMap<>(); |
| | | for (OutTaskParam outTaskParam : params) { |
| | | R r = outOrder(outTaskParam, n); |
| | | batchLineCounts.merge(buildOutOrderBatchKey(outTaskParam), 1, Integer::sum); |
| | | } |
| | | for (OutTaskParam outTaskParam : params) { |
| | | int count = batchLineCounts.getOrDefault(buildOutOrderBatchKey(outTaskParam), n); |
| | | R r = outOrder(outTaskParam, count); |
| | | if (!Objects.equals(r.get("code"), 200)) { |
| | | throw new CoolException("出库建单失败"); |
| | | } |
| | | } |
| | | return R.ok(); |
| | | } |
| | | |
| | | /** |
| | | * 7.9 出库异常变动上报。 |
| | | * WCS 只传 palletId、errorMsg,WMS 解析 orderId 后转发 ERP。 |
| | | */ |
| | | @Override |
| | | public R outOrderAbnormalReport(OutOrderAbnormalReportParam param) { |
| | | if (param == null || Cools.isEmpty(param.getPalletId())) { |
| | | return R.error("palletId不能为空"); |
| | | } |
| | | String orderId = resolveOutboundOrderId(param.getPalletId()); |
| | | if (Cools.isEmpty(orderId)) { |
| | | return R.error("未找到托盘对应出库单号:" + param.getPalletId()); |
| | | } |
| | | if (Cools.isEmpty(erpOutErrorAddress)) { |
| | | return R.error("ERP出库异常上报地址未配置"); |
| | | } |
| | | |
| | | ErpOutOrderAbnormalReportParam erpParam = new ErpOutOrderAbnormalReportParam(); |
| | | erpParam.setPalletId(param.getPalletId()); |
| | | erpParam.setErrorMsg(param.getErrorMsg()); |
| | | erpParam.setOrderId(orderId); |
| | | |
| | | String requestJson = JSON.toJSONString(erpParam); |
| | | String response = ""; |
| | | boolean pushOk = false; |
| | | String errorMsg = null; |
| | | String pushUrl = buildErpOutErrorRequestUrl(); |
| | | try { |
| | | response = new HttpHandler.Builder() |
| | | .setUri(erpUrl) |
| | | .setPath(erpOutErrorAddress) |
| | | .setJson(requestJson) |
| | | .build() |
| | | .doPost(); |
| | | pushOk = isErpCallSuccess(response); |
| | | if (!pushOk) { |
| | | errorMsg = extractErpCallError(response); |
| | | } |
| | | } catch (Exception e) { |
| | | errorMsg = e.getMessage(); |
| | | log.error("推ERP出库异常上报失败, palletId={}, orderId={}", param.getPalletId(), orderId, e); |
| | | } finally { |
| | | try { |
| | | apiLogService.save( |
| | | "推ERP-出库异常上报", |
| | | pushUrl, |
| | | null, |
| | | "127.0.0.1", |
| | | requestJson, |
| | | response, |
| | | pushOk, |
| | | "palletId=" + param.getPalletId() + ",orderId=" + orderId |
| | | ); |
| | | } catch (Exception logEx) { |
| | | log.error("save out abnormal api log failed", logEx); |
| | | } |
| | | } |
| | | if (!pushOk) { |
| | | return R.error(Cools.isEmpty(errorMsg) ? "ERP出库异常上报失败" : errorMsg); |
| | | } |
| | | return R.ok().add(Cools.add("palletId", param.getPalletId()).add("orderId", orderId)); |
| | | } |
| | | |
| | | /** |
| | | * 7.10 出库异常变动处理。 |
| | | * 本期只接收、校验和记录,不改本地任务,也不转发 WCS。 |
| | | */ |
| | | @Override |
| | | public R outOrderAbnormalHandle(OutOrderAbnormalHandleParam param) { |
| | | if (param == null || Cools.isEmpty(param.getPalletId())) { |
| | | return R.error("palletId不能为空"); |
| | | } |
| | | if (param.getOperateType() == null || !(Objects.equals(param.getOperateType(), 0) || Objects.equals(param.getOperateType(), 2))) { |
| | | return R.error("operateType只允许0或2"); |
| | | } |
| | | if (!existsOutboundPallet(param.getPalletId())) { |
| | | return R.error("未找到托盘:" + param.getPalletId()); |
| | | } |
| | | log.info("接收出库异常处理指令, palletId={}, operateType={}", param.getPalletId(), param.getOperateType()); |
| | | return R.ok().add(Cools.add("palletId", param.getPalletId()).add("operateType", param.getOperateType())); |
| | | } |
| | | |
| | | private String buildOutOrderBatchKey(OutTaskParam param) { |
| | | return param.getOrderId() + "#" + param.getBatchSeq(); |
| | | } |
| | | |
| | | private String resolveOutboundOrderId(String palletId) { |
| | | List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>() |
| | | .eq("io_type", 101) |
| | | .eq("barcode", palletId) |
| | | .orderBy("io_time", false) |
| | | .orderBy("wrk_no", false)); |
| | | if (!Cools.isEmpty(wrkMasts)) { |
| | | for (WrkMast wrkMast : wrkMasts) { |
| | | if (!Cools.isEmpty(wrkMast.getUserNo())) { |
| | | return wrkMast.getUserNo(); |
| | | } |
| | | } |
| | | } |
| | | List<WrkDetl> wrkDetls = wrkDetlService.selectList(new EntityWrapper<WrkDetl>() |
| | | .eq("zpallet", palletId) |
| | | .orderBy("io_time", false) |
| | | .orderBy("wrk_no", false)); |
| | | if (!Cools.isEmpty(wrkDetls)) { |
| | | for (WrkDetl wrkDetl : wrkDetls) { |
| | | if (!Cools.isEmpty(wrkDetl.getOrderNo())) { |
| | | return wrkDetl.getOrderNo(); |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private boolean existsOutboundPallet(String palletId) { |
| | | if (locDetlService.selectCount(new EntityWrapper<LocDetl>().eq("zpallet", palletId)) > 0) { |
| | | return true; |
| | | } |
| | | if (wrkMastService.selectCount(new EntityWrapper<WrkMast>().eq("io_type", 101).eq("barcode", palletId)) > 0) { |
| | | return true; |
| | | } |
| | | return wrkDetlService.selectCount(new EntityWrapper<WrkDetl>().eq("zpallet", palletId)) > 0; |
| | | } |
| | | |
| | | private boolean isErpCallSuccess(String response) { |
| | | if (Cools.isEmpty(response)) { |
| | | return false; |
| | | } |
| | | try { |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (jsonObject == null) { |
| | | return false; |
| | | } |
| | | Boolean success = jsonObject.getBoolean("success"); |
| | | if (success != null) { |
| | | return success; |
| | | } |
| | | Integer code = jsonObject.getInteger("code"); |
| | | if (code != null) { |
| | | return code == 200 || code == 0; |
| | | } |
| | | Integer status = jsonObject.getInteger("status"); |
| | | if (status != null) { |
| | | return status == 200 || status == 0; |
| | | } |
| | | String result = jsonObject.getString("result"); |
| | | if (!Cools.isEmpty(result)) { |
| | | return "success".equalsIgnoreCase(result) || "ok".equalsIgnoreCase(result) || "true".equalsIgnoreCase(result); |
| | | } |
| | | } catch (Exception ignore) { |
| | | return response.toLowerCase().contains("success") && response.toLowerCase().contains("true"); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private String extractErpCallError(String response) { |
| | | if (Cools.isEmpty(response)) { |
| | | return "empty erp response"; |
| | | } |
| | | try { |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (jsonObject == null) { |
| | | return response; |
| | | } |
| | | String[] keys = new String[]{"msg", "message", "error", "errMsg"}; |
| | | for (String key : keys) { |
| | | String value = jsonObject.getString(key); |
| | | if (!Cools.isEmpty(value)) { |
| | | return value; |
| | | } |
| | | } |
| | | } catch (Exception ignore) { |
| | | } |
| | | return response; |
| | | } |
| | | |
| | | private String buildErpOutErrorRequestUrl() { |
| | | if (Cools.isEmpty(erpUrl)) { |
| | | return erpOutErrorAddress; |
| | | } |
| | | if (erpOutErrorAddress == null) { |
| | | return erpUrl; |
| | | } |
| | | if (erpUrl.endsWith("/") && erpOutErrorAddress.startsWith("/")) { |
| | | return erpUrl + erpOutErrorAddress.substring(1); |
| | | } |
| | | if (!erpUrl.endsWith("/") && !erpOutErrorAddress.startsWith("/")) { |
| | | return erpUrl + "/" + erpOutErrorAddress; |
| | | } |
| | | return erpUrl + erpOutErrorAddress; |
| | | } |
| | | } |
| | | |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | public int outboundSeqMaxContiguousPlt(String userNo) { |
| | | List<Integer> fromMast = baseMapper.listOutboundPltTypesByUserNo(userNo); |
| | | List<Integer> fromLog = wrkMastLogMapper.listOutboundPltTypesByUserNo(userNo); |
| | | public int outboundSeqMaxContiguousPlt(String userNo, String batchSeq) { |
| | | List<Integer> fromMast = baseMapper.listOutboundPltTypesByUserNo(userNo, batchSeq); |
| | | List<Integer> fromLog = wrkMastLogMapper.listOutboundPltTypesByUserNo(userNo, batchSeq); |
| | | Set<Integer> filled = new HashSet<>(); |
| | | addPositivePlt(fromMast, filled); |
| | | addPositivePlt(fromLog, filled); |
| | |
| | | * <p> |
| | | * 当前批量下发的归并维度是: |
| | | * 1. WCS接口路径(入库/出库/移库不能混发); |
| | | * 2. work_mast.user_no(相同 userNo 的任务必须放到同一批次一起上报)。 |
| | | * 2. work_mast.user_no + batch_seq(相同订单同批次的任务必须放到同一批次一起上报)。 |
| | | * |
| | | * @author Ryan |
| | | * @date 2026/1/10 14:42 |
| | |
| | | // 仅处理待下发/已生成下发号的工作档。 |
| | | List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>().in("wrk_sts", Arrays.asList(1L, 11L)) |
| | | .orderBy("user_no", true) |
| | | .orderBy("batch_seq", true) |
| | | .orderBy("plt_type", true)); |
| | | if (wrkMasts.isEmpty()) { |
| | | return; |
| | |
| | | String wcsLocNo = Cools.isEmpty(wrkMast.getLocNo()) ? "" : Utils.WMSLocToWCSLoc(wrkMast.getLocNo()); |
| | | WorkTaskParams params = new WorkTaskParams(); |
| | | |
| | | // 101: 出库。此处 batch 字段承载 userNo,后续 service 层会据此把相同 userNo 的任务并到一批。 |
| | | // 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()) |
| | | .setBatch(wrkMast.getUserNo()) |
| | | .setBatchSeq(wrkMast.getPltType()) |
| | | .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") |
| New file |
| | |
| | | package com.zy.asrs.task; |
| | | |
| | | import com.baomidou.mybatisplus.mapper.EntityWrapper; |
| | | import com.zy.asrs.entity.WrkMast; |
| | | import com.zy.asrs.service.WrkMastService; |
| | | import com.zy.asrs.task.core.ReturnT; |
| | | import com.zy.asrs.task.handler.WorkOutLockErpReportHandler; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.scheduling.annotation.Scheduled; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | |
| | | @Slf4j |
| | | @Component |
| | | public class WorkOutLockErpReportScheduler { |
| | | |
| | | @Autowired |
| | | private WrkMastService wrkMastService; |
| | | |
| | | @Autowired |
| | | private WorkOutLockErpReportHandler workOutLockErpReportHandler; |
| | | |
| | | /** |
| | | * 7.12 出库任务锁定。 |
| | | * WCS 上报出库任务开始后,WMS 将 wrk_sts=13 的出库任务锁定信息上报 ERP。 |
| | | */ |
| | | @Scheduled(cron = "0/10 * * * * ? ") |
| | | private void execute() { |
| | | List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>() |
| | | .eq("wrk_sts", WorkOutLockErpReportHandler.ERP_LOCK_REPORT_PENDING_WRK_STS) |
| | | .in("io_type", Arrays.asList(101, 110)) |
| | | .orderBy("io_time", true) |
| | | .orderBy("wrk_no", true)); |
| | | if (wrkMasts.isEmpty()) { |
| | | return; |
| | | } |
| | | for (WrkMast wrkMast : wrkMasts) { |
| | | ReturnT<String> result = workOutLockErpReportHandler.start(wrkMast); |
| | | if (!result.isSuccess()) { |
| | | log.error("workNo={} outbound erp lock report failed: {}", wrkMast.getWrkNo(), result.getMsg()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | if (m.getIoPri() != null) { |
| | | t.put("taskPri", m.getIoPri().intValue()); |
| | | } |
| | | t.put("batchSeq", m.getPltType()); |
| | | if (m.getUserNo() != null) { |
| | | // 7.11:seq=0 表示无序,WCS 下发不传 batch 和 batchSeq。 |
| | | if (m.getPltType() != null && m.getPltType() > 0) { |
| | | t.put("batchSeq", m.getPltType()); |
| | | } |
| | | if (m.getPltType() != null && m.getPltType() > 0 && m.getUserNo() != null) { |
| | | t.put("batch", m.getUserNo()); |
| | | } |
| | | tasks.add(t); |
| | |
| | | @Service |
| | | public class NotifyLogHandler extends AbstractHandler<String> { |
| | | |
| | | private static final String WAIT_PAKIN_ARCHIVE_COLUMNS = |
| | | "zpallet, anfme, loc_no, matnr, maktx, batch, order_no, specs, model, color, brand, unit, price, sku, units, barcode, " + |
| | | "origin, manu, manu_date, item_num, safe_qty, weight, man_length, volume, three_code, supp, supp_code, be_batch, dead_time, " + |
| | | "dead_warn, source, inspect, danger, status, io_status, modi_time, modi_user, appe_time, appe_user, memo, standby1, standby2, " + |
| | | "standby3, box_type1, box_type2, box_type3"; |
| | | |
| | | private static final String ARCHIVE_FINISHED_WAIT_PAKIN_SQL = |
| | | "insert into cust_wait_pakin_log (" + WAIT_PAKIN_ARCHIVE_COLUMNS + ") " + |
| | | "select " + WAIT_PAKIN_ARCHIVE_COLUMNS + " from cust_wait_pakin where io_status = 'F'"; |
| | | |
| | | @Autowired |
| | | private JdbcTemplate jdbcTemplate; |
| | | |
| | |
| | | public ReturnT<String> start() { |
| | | try { |
| | | // 入库通知档转历史档 |
| | | int pakInLog = jdbcTemplate.update("insert into cust_wait_pakin_log select * from cust_wait_pakin where io_status = 'F';"); |
| | | int pakInLog = jdbcTemplate.update(ARCHIVE_FINISHED_WAIT_PAKIN_SQL); |
| | | if (pakInLog > 0) { |
| | | int pakInDelete = jdbcTemplate.update("delete from cust_wait_pakin where io_status = 'F';"); |
| | | if (pakInDelete <= 0) { |
| | |
| | | @Value("${erp.address.Outaddress}") |
| | | private String erpOutAddress; |
| | | |
| | | /** |
| | | * 7.7 出库完成更新。 |
| | | * WMS 在出库完成后向 ERP 上报 palletId、createTime、startTime、orderId。 |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public ReturnT<String> start(WrkMast source) { |
| | | WrkMast wrkMast = wrkMastService.selectById(source.getWrkNo()); |
| | |
| | | if (wrkDetls != null && wrkDetls.size() > 0) { |
| | | param.setOrderId(wrkDetls.get(0).getOrderNo()); |
| | | } |
| | | if (Cools.isEmpty(param.getOrderId())) { |
| | | param.setOrderId(wrkMast.getUserNo()); |
| | | } |
| | | String request = JSON.toJSONString(param); |
| | | String response = ""; |
| | | boolean success = false; |
| New file |
| | |
| | | package com.zy.asrs.task.handler; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.core.common.Cools; |
| | | import com.zy.asrs.entity.WrkDetl; |
| | | import com.zy.asrs.entity.WrkMast; |
| | | import com.zy.asrs.entity.param.ErpOutTaskLockReportParam; |
| | | import com.zy.asrs.service.ApiLogService; |
| | | import com.zy.asrs.service.WrkDetlService; |
| | | import com.zy.asrs.service.WrkMastService; |
| | | import com.zy.asrs.task.AbstractHandler; |
| | | import com.zy.asrs.task.core.ReturnT; |
| | | import com.zy.common.entity.Parameter; |
| | | import com.zy.common.utils.HttpHandler; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | | @Slf4j |
| | | @Service |
| | | public class WorkOutLockErpReportHandler extends AbstractHandler<String> { |
| | | |
| | | public static final long ERP_LOCK_REPORT_PENDING_WRK_STS = 13L; |
| | | public static final long ERP_LOCK_REPORT_SUCCESS_WRK_STS = 21L; |
| | | public static final long ERP_LOCK_REPORT_FAIL_WRK_STS = 22L; |
| | | public static final int ERP_LOCK_REPORT_MAX_RETRY_TIMES = 3; |
| | | public static final String ERP_LOCK_REPORT_PENDING_FLAG = "P"; |
| | | public static final String ERP_LOCK_REPORT_SUCCESS_FLAG = "Y"; |
| | | public static final String ERP_LOCK_REPORT_FAIL_FLAG = "F"; |
| | | |
| | | private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; |
| | | |
| | | @Autowired |
| | | private WrkMastService wrkMastService; |
| | | @Autowired |
| | | private WrkDetlService wrkDetlService; |
| | | @Autowired |
| | | private ApiLogService apiLogService; |
| | | |
| | | @Value("${erp.switch.ErpReportOld}") |
| | | private boolean erpReportOld; |
| | | |
| | | @Value("${erp.address.URL:}") |
| | | private String erpUrl; |
| | | |
| | | @Value("${erp.address.OutLockaddress:}") |
| | | private String erpOutLockAddress; |
| | | |
| | | /** |
| | | * 7.12 出库任务锁定。 |
| | | * WCS 出库任务开始后,WMS 向 ERP 上报 palletId、orderId;失败最多重试三次。 |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public ReturnT<String> start(WrkMast source) { |
| | | WrkMast wrkMast = wrkMastService.selectById(source.getWrkNo()); |
| | | if (wrkMast == null || !Long.valueOf(ERP_LOCK_REPORT_PENDING_WRK_STS).equals(wrkMast.getWrkSts())) { |
| | | return SUCCESS; |
| | | } |
| | | |
| | | if (!isErpReportEnabled()) { |
| | | finishReport(wrkMast, false, getRetryTimes(wrkMast), "ERP reporting is disabled", false); |
| | | return FAIL.setMsg("ERP reporting is disabled"); |
| | | } |
| | | if (Cools.isEmpty(erpOutLockAddress)) { |
| | | return failWithoutCallingErp(wrkMast, "ERP出库任务锁定上报地址未配置"); |
| | | } |
| | | |
| | | List<WrkDetl> wrkDetls = wrkDetlService.selectByWrkNo(wrkMast.getWrkNo()); |
| | | ErpOutTaskLockReportParam param = buildParam(wrkMast, wrkDetls); |
| | | String validateMsg = validateParam(param); |
| | | if (!Cools.isEmpty(validateMsg)) { |
| | | return failWithoutCallingErp(wrkMast, validateMsg); |
| | | } |
| | | |
| | | String request = JSON.toJSONString(param); |
| | | String response = ""; |
| | | boolean success = false; |
| | | String errorMsg = null; |
| | | String requestUrl = buildRequestUrl(); |
| | | |
| | | try { |
| | | response = new HttpHandler.Builder() |
| | | .setUri(erpUrl) |
| | | .setPath(erpOutLockAddress) |
| | | .setJson(request) |
| | | .build() |
| | | .doPost(); |
| | | success = isErpReportSuccess(response); |
| | | if (!success) { |
| | | errorMsg = extractErrorMsg(response); |
| | | } |
| | | finishReport(wrkMast, success, getRetryTimes(wrkMast), errorMsg, true); |
| | | } catch (Exception e) { |
| | | errorMsg = e.getMessage(); |
| | | finishReport(wrkMast, false, getRetryTimes(wrkMast), errorMsg, true); |
| | | } finally { |
| | | try { |
| | | apiLogService.save( |
| | | "Outbound ERP Lock Report", |
| | | requestUrl, |
| | | null, |
| | | "127.0.0.1", |
| | | request, |
| | | response, |
| | | success, |
| | | "workNo=" + wrkMast.getWrkNo() |
| | | ); |
| | | } catch (Exception logEx) { |
| | | log.error("save outbound erp lock api log failed", logEx); |
| | | } |
| | | } |
| | | |
| | | if (success) { |
| | | return SUCCESS; |
| | | } |
| | | return FAIL.setMsg(errorMsg); |
| | | } |
| | | |
| | | private ReturnT<String> failWithoutCallingErp(WrkMast wrkMast, String errorMsg) { |
| | | finishReport(wrkMast, false, getRetryTimes(wrkMast), errorMsg, true); |
| | | return FAIL.setMsg(errorMsg); |
| | | } |
| | | |
| | | private boolean isErpReportEnabled() { |
| | | if (!erpReportOld) { |
| | | return false; |
| | | } |
| | | String erpReport = Parameter.get().getErpReport(); |
| | | return Cools.isEmpty(erpReport) || "true".equalsIgnoreCase(erpReport); |
| | | } |
| | | |
| | | private ErpOutTaskLockReportParam buildParam(WrkMast wrkMast, List<WrkDetl> wrkDetls) { |
| | | ErpOutTaskLockReportParam param = new ErpOutTaskLockReportParam(); |
| | | param.setPalletId(resolvePalletId(wrkMast, wrkDetls)); |
| | | param.setOrderId(resolveOrderId(wrkMast, wrkDetls)); |
| | | param.setStartTime(formatDate(new Date())); |
| | | return param; |
| | | } |
| | | |
| | | private String formatDate(Date date) { |
| | | if (date == null) { |
| | | return null; |
| | | } |
| | | return new SimpleDateFormat(DATE_TIME_PATTERN).format(date); |
| | | } |
| | | |
| | | private String resolvePalletId(WrkMast wrkMast, List<WrkDetl> wrkDetls) { |
| | | if (!Cools.isEmpty(wrkMast.getBarcode())) { |
| | | return wrkMast.getBarcode(); |
| | | } |
| | | if (wrkDetls == null) { |
| | | return null; |
| | | } |
| | | for (WrkDetl wrkDetl : wrkDetls) { |
| | | if (!Cools.isEmpty(wrkDetl.getZpallet())) { |
| | | return wrkDetl.getZpallet(); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String resolveOrderId(WrkMast wrkMast, List<WrkDetl> wrkDetls) { |
| | | if (wrkDetls != null) { |
| | | for (WrkDetl wrkDetl : wrkDetls) { |
| | | if (!Cools.isEmpty(wrkDetl.getOrderNo())) { |
| | | return wrkDetl.getOrderNo(); |
| | | } |
| | | } |
| | | } |
| | | return wrkMast.getUserNo(); |
| | | } |
| | | |
| | | private String validateParam(ErpOutTaskLockReportParam param) { |
| | | if (param == null || Cools.isEmpty(param.getPalletId())) { |
| | | return "ERP出库任务锁定上报失败:palletId为空"; |
| | | } |
| | | if (Cools.isEmpty(param.getOrderId())) { |
| | | return "ERP出库任务锁定上报失败:orderId为空"; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private boolean isErpReportSuccess(String response) { |
| | | if (Cools.isEmpty(response)) { |
| | | return false; |
| | | } |
| | | try { |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (jsonObject == null) { |
| | | return false; |
| | | } |
| | | Boolean success = jsonObject.getBoolean("success"); |
| | | if (success != null) { |
| | | return success; |
| | | } |
| | | Integer code = jsonObject.getInteger("code"); |
| | | if (code != null) { |
| | | return code == 200 || code == 0; |
| | | } |
| | | Integer status = jsonObject.getInteger("status"); |
| | | if (status != null) { |
| | | return status == 200 || status == 0; |
| | | } |
| | | String result = jsonObject.getString("result"); |
| | | if (!Cools.isEmpty(result)) { |
| | | return "success".equalsIgnoreCase(result) || "ok".equalsIgnoreCase(result) || "true".equalsIgnoreCase(result); |
| | | } |
| | | } catch (Exception ignore) { |
| | | return response.toLowerCase().contains("success") && response.toLowerCase().contains("true"); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private String extractErrorMsg(String response) { |
| | | if (Cools.isEmpty(response)) { |
| | | return "empty erp response"; |
| | | } |
| | | try { |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (jsonObject == null) { |
| | | return response; |
| | | } |
| | | String[] keys = new String[]{"msg", "message", "error", "errMsg"}; |
| | | for (String key : keys) { |
| | | String value = jsonObject.getString(key); |
| | | if (!Cools.isEmpty(value)) { |
| | | return value; |
| | | } |
| | | } |
| | | } catch (Exception ignore) { |
| | | } |
| | | return response; |
| | | } |
| | | |
| | | private int getRetryTimes(WrkMast wrkMast) { |
| | | if (wrkMast.getExpTime() == null) { |
| | | return 0; |
| | | } |
| | | return wrkMast.getExpTime().intValue(); |
| | | } |
| | | |
| | | private void finishReport(WrkMast wrkMast, boolean success, int currentRetryTimes, String errorMsg, boolean countCurrentAttempt) { |
| | | int retryTimes = currentRetryTimes + (countCurrentAttempt ? 1 : 0); |
| | | Date now = new Date(); |
| | | wrkMast.setExpTime((double) retryTimes); |
| | | wrkMast.setModiTime(now); |
| | | if (success) { |
| | | wrkMast.setWrkSts(ERP_LOCK_REPORT_SUCCESS_WRK_STS); |
| | | wrkMast.setLogMk(ERP_LOCK_REPORT_SUCCESS_FLAG); |
| | | wrkMast.setLogErrMemo(null); |
| | | wrkMast.setLogErrTime(null); |
| | | } else { |
| | | wrkMast.setLogErrMemo(truncate(errorMsg, 500)); |
| | | wrkMast.setLogErrTime(now); |
| | | if (retryTimes >= ERP_LOCK_REPORT_MAX_RETRY_TIMES || !countCurrentAttempt) { |
| | | wrkMast.setWrkSts(ERP_LOCK_REPORT_FAIL_WRK_STS); |
| | | wrkMast.setLogMk(ERP_LOCK_REPORT_FAIL_FLAG); |
| | | } else { |
| | | wrkMast.setWrkSts(ERP_LOCK_REPORT_PENDING_WRK_STS); |
| | | wrkMast.setLogMk(ERP_LOCK_REPORT_PENDING_FLAG); |
| | | } |
| | | } |
| | | if (!wrkMastService.updateById(wrkMast)) { |
| | | throw new IllegalStateException("update outbound erp lock report status failed, workNo=" + wrkMast.getWrkNo()); |
| | | } |
| | | } |
| | | |
| | | private String truncate(String message, int maxLength) { |
| | | if (message == null || message.length() <= maxLength) { |
| | | return message; |
| | | } |
| | | return message.substring(0, maxLength); |
| | | } |
| | | |
| | | private String buildRequestUrl() { |
| | | if (Cools.isEmpty(erpUrl)) { |
| | | return erpOutLockAddress; |
| | | } |
| | | if (erpOutLockAddress == null) { |
| | | return erpUrl; |
| | | } |
| | | if (erpUrl.endsWith("/") && erpOutLockAddress.startsWith("/")) { |
| | | return erpUrl + erpOutLockAddress.substring(1); |
| | | } |
| | | if (!erpUrl.endsWith("/") && !erpOutLockAddress.startsWith("/")) { |
| | | return erpUrl + "/" + erpOutLockAddress; |
| | | } |
| | | return erpUrl + erpOutLockAddress; |
| | | } |
| | | } |
| | |
| | | // 源站点状态检测 |
| | | BasDevp sourceStaNo = basDevpService.checkSiteStatus(devpNo, true); |
| | | // 检索库位 |
| | | List<String> matnrs = waitPakins.stream().map(WaitPakin::getMatnr).distinct().collect(Collectors.toList()); |
| | | List<String> batchs = waitPakins.stream().map(WaitPakin::getBatch).distinct().collect(Collectors.toList()); |
| | | FindLocNoAttributeVo findLocNoAttributeVo = new FindLocNoAttributeVo(matnrs.get(0), batchs.get(0)); |
| | | // FindLocNoAttributeVo findLocNoAttributeVo = new FindLocNoAttributeVo(waitPakins.get(0)); |
| | | FindLocNoAttributeVo findLocNoAttributeVo = new FindLocNoAttributeVo(waitPakins.get(0)); |
| | | // IoT 指定了目标库位时优先尝试该库位;不可用时再退回现有自动找位规则。 |
| | | StartupDto dto = buildPreferredStartupDto(devpNo, extractPreferredInboundLoc(waitPakins)); |
| | | if (dto == null) { |
| | |
| | | Inaddress: /api/Service/InPalletCompleted |
| | | #出库上报 |
| | | Outaddress: /api/Service/OutPalletCompleted |
| | | #出库异常上报 |
| | | OutErroraddress: /api/Service/OutPalletAbnormal |
| | | #出库任务锁定上报 |
| | | OutLockaddress: /api/Service/OutPalletLocked |
| | | |
| | | #wcs任务下发 |
| | | wcs: |
| | |
| | | <result column="upd_mk" property="updMk" /> |
| | | <result column="exit_mk" property="exitMk" /> |
| | | <result column="plt_type" property="pltType" /> |
| | | <result column="batch_seq" property="batchSeq" /> |
| | | <result column="empty_mk" property="emptyMk" /> |
| | | <result column="io_time" property="ioTime" /> |
| | | <result column="ctn_type" property="ctnType" /> |
| | |
| | | <result column="upd_mk" property="updMk" /> |
| | | <result column="exit_mk" property="exitMk" /> |
| | | <result column="plt_type" property="pltType" /> |
| | | <result column="batch_seq" property="batchSeq" /> |
| | | <result column="empty_mk" property="emptyMk" /> |
| | | <result column="io_time" property="ioTime" /> |
| | | <result column="ctn_type" property="ctnType" /> |
| New file |
| | |
| | | IF COL_LENGTH('dbo.asr_wrk_mast', 'batch_seq') IS NULL |
| | | BEGIN |
| | | ALTER TABLE [dbo].[asr_wrk_mast] |
| | | ADD [batch_seq] NVARCHAR(100) NULL; |
| | | END |
| | | ELSE |
| | | BEGIN |
| | | ALTER TABLE [dbo].[asr_wrk_mast] |
| | | ALTER COLUMN [batch_seq] NVARCHAR(100) NULL; |
| | | END |
| | | GO |
| | | |
| | | IF COL_LENGTH('dbo.asr_wrk_mast_log', 'batch_seq') IS NULL |
| | | BEGIN |
| | | ALTER TABLE [dbo].[asr_wrk_mast_log] |
| | | ADD [batch_seq] NVARCHAR(100) NULL; |
| | | END |
| | | ELSE |
| | | BEGIN |
| | | ALTER TABLE [dbo].[asr_wrk_mast_log] |
| | | ALTER COLUMN [batch_seq] NVARCHAR(100) NULL; |
| | | END |
| | | GO |
| New file |
| | |
| | | IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id = OBJECT_ID(N'dbo.asr_bas_wrk_status') AND name = N'wrk_sts') |
| | | BEGIN |
| | | PRINT 'asr_bas_wrk_status not found, skip 7.12 status initialization'; |
| | | END |
| | | ELSE |
| | | BEGIN |
| | | IF NOT EXISTS (SELECT 1 FROM asr_bas_wrk_status WHERE wrk_sts = 21) |
| | | BEGIN |
| | | INSERT INTO asr_bas_wrk_status (wrk_sts, wrk_desc, modi_time, appe_time) |
| | | VALUES (21, N'给ERP开始任务下发成功', GETDATE(), GETDATE()); |
| | | END |
| | | |
| | | IF NOT EXISTS (SELECT 1 FROM asr_bas_wrk_status WHERE wrk_sts = 22) |
| | | BEGIN |
| | | INSERT INTO asr_bas_wrk_status (wrk_sts, wrk_desc, modi_time, appe_time) |
| | | VALUES (22, N'给ERP开始任务下发失败', GETDATE(), GETDATE()); |
| | | END |
| | | END |