| | |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | | import java.util.*; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.stream.Collectors; |
| | | |
| | |
| | | */ |
| | | @Override |
| | | public R pubWrkToWcs(WorkTaskParams params) { |
| | | if (Objects.isNull(params.getTaskNo())) { |
| | | return R.error("任务号不能为空!!"); |
| | | if (params == null) { |
| | | return R.error("参数不能为空!!"); |
| | | } |
| | | if (Objects.isNull(params.getBarcode())) { |
| | | return R.error("托盘码不能为空!!"); |
| | | WrkMast wrkMast = wrkMastService.selectOne(new EntityWrapper<WrkMast>().eq("wrk_no", params.getTaskNo())); |
| | | String validateMsg = validatePubTask(params, wrkMast); |
| | | if (!Cools.isEmpty(validateMsg)) { |
| | | return R.error(validateMsg); |
| | | } |
| | | if (Objects.isNull(params.getLocNo())) { |
| | | return R.error("目标库位不能为空!!"); |
| | | } |
| | | String url = createInTask; |
| | | if (!Objects.isNull(params.getType()) && params.getType().equals("out")) { |
| | | url = getWcs_address; |
| | | }else if (!Objects.isNull(params.getType()) && params.getType().equals("move")) { |
| | | url = createLocMoveTask; |
| | | } |
| | | String url = resolveTaskPath(params); |
| | | String response; |
| | | R r = R.ok(); |
| | | WrkMast wrkMast = wrkMastService.selectOne(new EntityWrapper<WrkMast>().eq("wrk_no", params.getTaskNo())); |
| | | if (!Objects.isNull(wrkMast) && "out".equalsIgnoreCase(params.getType()) && "Y".equalsIgnoreCase(wrkMast.getPauseMk())) { |
| | | return R.error("task paused"); |
| | | } |
| | | try { |
| | | log.info("下发搬运任务给wcs="+JSON.toJSONString(params)); |
| | | response = new HttpHandler.Builder() |
| | | .setUri(wcs_address) |
| | | .setPath(url) |
| | | .setHttps(wcs_address != null && wcs_address.startsWith("https://")) |
| | | .setTimeout(10, TimeUnit.SECONDS) |
| | | .setJson(JSON.toJSONString(params)) |
| | | .build() |
| | | .doPost(); |
| | |
| | | Integer code = jsonObject.getInteger("code"); |
| | | |
| | | if (code==200) { |
| | | if (!Objects.isNull(wrkMast)) { |
| | | if (wrkMast.getIoType()==1 || wrkMast.getIoType()==10) { |
| | | wrkMast.setWrkSts(2L); |
| | | wrkMast.setModiTime(new Date()); |
| | | wrkMastService.updateById(wrkMast); |
| | | }else if(wrkMast.getIoType()==2){ |
| | | wrkMast.setWrkSts(2L); |
| | | wrkMast.setModiTime(new Date()); |
| | | wrkMastService.updateById(wrkMast); |
| | | }else if (wrkMast.getIoType()==101 || wrkMast.getIoType()==110) { |
| | | wrkMast.setWrkSts(12L); |
| | | wrkMast.setModiTime(new Date()); |
| | | wrkMastService.updateById(wrkMast); |
| | | } |
| | | } |
| | | updateWrkMastAfterPublish(wrkMast); |
| | | //TODO 上报是否成功 |
| | | }else { |
| | | r =R.error(); |
| | |
| | | throw new RuntimeException(e); |
| | | } |
| | | return r; |
| | | } |
| | | |
| | | @Override |
| | | public R pubWrksToWcs(List<WorkTaskParams> paramsList) { |
| | | if (paramsList == null || paramsList.isEmpty()) { |
| | | return R.error("任务不能为空!!"); |
| | | } |
| | | |
| | | // 先一次性把本批任务对应的工作档捞出来,避免循环内重复查库。 |
| | | Map<String, WrkMast> wrkMastMap = getWrkMastMap(paramsList); |
| | | Map<String, List<WorkTaskParams>> groupedTasks = new LinkedHashMap<>(); |
| | | List<String> skipMsgs = new ArrayList<>(); |
| | | |
| | | for (WorkTaskParams params : paramsList) { |
| | | if (params == null) { |
| | | skipMsgs.add("任务不能为空!!"); |
| | | continue; |
| | | } |
| | | WrkMast wrkMast = wrkMastMap.get(params.getTaskNo()); |
| | | String validateMsg = validatePubTask(params, wrkMast); |
| | | if (!Cools.isEmpty(validateMsg)) { |
| | | skipMsgs.add(buildTaskMsg(params, validateMsg)); |
| | | continue; |
| | | } |
| | | |
| | | // 分组主键 = 接口路径 + userNo。 |
| | | // 这样既能保证入库/出库/移库不会混发,也能保证相同 userNo 的任务会打包到同一次 WCS 请求中。 |
| | | String groupKey = buildBatchGroupKey(params, wrkMast); |
| | | groupedTasks.computeIfAbsent(groupKey, key -> new ArrayList<>()).add(params); |
| | | } |
| | | |
| | | if (groupedTasks.isEmpty()) { |
| | | return R.error(skipMsgs.isEmpty() ? "无可下发任务" : skipMsgs.get(0)).add(skipMsgs); |
| | | } |
| | | |
| | | int successCount = 0; |
| | | List<String> failMsgs = new ArrayList<>(); |
| | | for (List<WorkTaskParams> group : groupedTasks.values()) { |
| | | if (group == null || group.isEmpty()) { |
| | | continue; |
| | | } |
| | | |
| | | // 同一组内的任务类型一致,因此取第一条即可确定本组应该调用哪个 WCS 接口。 |
| | | String path = resolveTaskPath(group.get(0)); |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | // WCS 批量下发报文统一使用 {"tasks":[...]} 结构。 |
| | | payload.put("tasks", buildTaskPayloads(group)); |
| | | String response = null; |
| | | try { |
| | | log.info("批量下发搬运任务给wcs={}", JSON.toJSONString(payload)); |
| | | response = new HttpHandler.Builder() |
| | | .setUri(wcs_address) |
| | | .setPath(path) |
| | | .setHttps(wcs_address != null && wcs_address.startsWith("https://")) |
| | | .setTimeout(10, TimeUnit.SECONDS) |
| | | .setJson(JSON.toJSONString(payload)) |
| | | .build() |
| | | .doPost(); |
| | | JSONObject jsonObject = JSON.parseObject(response == null ? "{}" : response); |
| | | log.info("批量下发任务给wcs的返回值={}", response); |
| | | Integer code = jsonObject.getInteger("code"); |
| | | if (code != null && code == 200) { |
| | | successCount += group.size(); |
| | | // 只有整组下发成功,才回写本地工作档状态,避免 WMS/WCS 状态分叉。 |
| | | for (WorkTaskParams params : group) { |
| | | updateWrkMastAfterPublish(wrkMastMap.get(params.getTaskNo())); |
| | | } |
| | | } else { |
| | | String msg = jsonObject.getString("msg"); |
| | | failMsgs.add("path=" + path + ", msg=" + (Cools.isEmpty(msg) ? "WCS下发任务失败" : msg)); |
| | | log.error("批量下发任务给wcs失败, path:{}, request:{}, response:{}", path, JSON.toJSONString(payload), response); |
| | | } |
| | | } catch (IOException e) { |
| | | failMsgs.add("path=" + path + ", msg=" + e.getMessage()); |
| | | log.error("批量下发任务给wcs异常, path:{}, request:{}, response:{}", path, JSON.toJSONString(payload), response, e); |
| | | } |
| | | } |
| | | |
| | | Map<String, Object> result = new HashMap<>(); |
| | | result.put("successCount", successCount); |
| | | result.put("skipCount", skipMsgs.size()); |
| | | result.put("failCount", failMsgs.size()); |
| | | if (!skipMsgs.isEmpty()) { |
| | | result.put("skipMsgs", skipMsgs); |
| | | } |
| | | if (!failMsgs.isEmpty()) { |
| | | result.put("failMsgs", failMsgs); |
| | | } |
| | | |
| | | if (successCount == 0) { |
| | | String msg = !failMsgs.isEmpty() ? failMsgs.get(0) : (skipMsgs.isEmpty() ? "WCS下发任务失败" : skipMsgs.get(0)); |
| | | return R.error(msg).add(result); |
| | | } |
| | | return R.ok(failMsgs.isEmpty() && skipMsgs.isEmpty() ? "操作成功" : "部分任务下发成功").add(result); |
| | | } |
| | | |
| | | /** |
| | |
| | | if (!wrkMastService.updateById(mast)) { |
| | | throw new CoolException("任务状态修改失败!!"); |
| | | } |
| | | //wcs任务取消接口 |
| | | } else if (params.getMsgType().equals("task_cancel")) { |
| | | if (mast.getIoType() != null && mast.getIoType() > 100 && mast.getWrkSts() < 14) { |
| | | mast.setPauseMk("Y"); |
| | | mast.setUpdMk("WCS_CANCELLED"); |
| | | mast.setManuType("WCS_CANCELLED"); |
| | | mast.setModiTime(new Date()); |
| | | if (!wrkMastService.updateById(mast)) { |
| | | throw new CoolException("task cancel update fail"); |
| | | } |
| | | } |
| | | }else if (params.getMsgType().equals("task_arrive")) { |
| | | workService.cancelWrkMast(String.valueOf(mast.getWrkNo()), 9955L); |
| | | } else if (params.getMsgType().equals("task_arrive")) { |
| | | //到达目的地 |
| | | //如果出库任务是跨区则需要生成新的入库任务入库 |
| | | if(!Cools.isEmpty(mast.getLocNo())){ |
| | |
| | | } |
| | | return R.ok(); |
| | | } |
| | | |
| | | private boolean requiresOutboundErpConfirm(WrkMast wrkMast) { |
| | | Integer ioType = wrkMast == null ? null : wrkMast.getIoType(); |
| | | return ioType != null && (ioType == 101 || ioType == 103 || ioType == 104 || ioType == 107 || ioType == 110); |
| | | } |
| | | |
| | | /** |
| | | * 校验单条任务是否满足下发前提。 |
| | | * <p> |
| | | * 这里既校验接口必填项,也校验业务约束,例如: |
| | | * 1. 出库任务是否被暂停; |
| | | * 2. 需要 ERP 确认的出库任务是否已确认。 |
| | | */ |
| | | private String validatePubTask(WorkTaskParams params, WrkMast wrkMast) { |
| | | if (params == null) { |
| | | return "参数不能为空!!"; |
| | | } |
| | | if (Cools.isEmpty(params.getTaskNo())) { |
| | | return "任务号不能为空!!"; |
| | | } |
| | | if (Cools.isEmpty(params.getBarcode())) { |
| | | return "托盘码不能为空!!"; |
| | | } |
| | | if (Cools.isEmpty(params.getLocNo())) { |
| | | return "目标库位不能为空!!"; |
| | | } |
| | | if (!Objects.isNull(wrkMast) && "out".equalsIgnoreCase(params.getType())) { |
| | | if ("Y".equalsIgnoreCase(wrkMast.getPauseMk())) { |
| | | return "task paused"; |
| | | } |
| | | if (requiresOutboundErpConfirm(wrkMast) && !"Y".equalsIgnoreCase(wrkMast.getPdcType())) { |
| | | return "task not confirmed by erp"; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 按任务类型选择 WCS 接口地址。 |
| | | * in -> 入库接口 |
| | | * out -> 出库接口 |
| | | * move -> 移库接口 |
| | | */ |
| | | private String resolveTaskPath(WorkTaskParams params) { |
| | | if (!Objects.isNull(params.getType()) && params.getType().equals("out")) { |
| | | return getWcs_address; |
| | | } |
| | | if (!Objects.isNull(params.getType()) && params.getType().equals("move")) { |
| | | return createLocMoveTask; |
| | | } |
| | | return createInTask; |
| | | } |
| | | |
| | | /** |
| | | * WCS 下发成功后推进本地工作档状态。 |
| | | * <p> |
| | | * 这里只处理“已下发”这一层状态,不处理设备执行完成状态; |
| | | * 设备执行完成依然以 WCS 回写为准。 |
| | | */ |
| | | private void updateWrkMastAfterPublish(WrkMast wrkMast) { |
| | | if (Objects.isNull(wrkMast)) { |
| | | return; |
| | | } |
| | | if (wrkMast.getIoType()==1 || wrkMast.getIoType()==10) { |
| | | wrkMast.setWrkSts(2L); |
| | | wrkMast.setModiTime(new Date()); |
| | | wrkMastService.updateById(wrkMast); |
| | | }else if(wrkMast.getIoType()==2){ |
| | | wrkMast.setWrkSts(2L); |
| | | wrkMast.setModiTime(new Date()); |
| | | wrkMastService.updateById(wrkMast); |
| | | }else if (wrkMast.getIoType()==101 || wrkMast.getIoType()==110) { |
| | | wrkMast.setWrkSts(12L); |
| | | wrkMast.setModiTime(new Date()); |
| | | wrkMastService.updateById(wrkMast); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 把本次待下发的 taskNo 批量映射成工作档,供后续校验、按 userNo 分组、状态回写复用。 |
| | | */ |
| | | private Map<String, WrkMast> getWrkMastMap(List<WorkTaskParams> paramsList) { |
| | | List<String> taskNos = paramsList.stream() |
| | | .filter(Objects::nonNull) |
| | | .map(WorkTaskParams::getTaskNo) |
| | | .filter(taskNo -> !Cools.isEmpty(taskNo)) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | if (taskNos.isEmpty()) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>().in("wrk_no", taskNos)); |
| | | if (wrkMasts == null || wrkMasts.isEmpty()) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | return wrkMasts.stream() |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toMap(mast -> String.valueOf(mast.getWrkNo()), mast -> mast, (left, right) -> left, LinkedHashMap::new)); |
| | | } |
| | | |
| | | /** |
| | | * 构造批量下发的分组键。 |
| | | * <p> |
| | | * 分组规则: |
| | | * 1. 先按接口路径区分,避免不同任务类型混用同一个 WCS 接口; |
| | | * 2. 再按 userNo 区分,确保相同 userNo 的任务一起上报。 |
| | | * <p> |
| | | * 正常情况下 userNo 取自 work_mast.user_no; |
| | | * 如果当前没查到工作档,则回退到请求里的 batch 字段,保证兼容已有调用。 |
| | | */ |
| | | private String buildBatchGroupKey(WorkTaskParams params, WrkMast wrkMast) { |
| | | String path = resolveTaskPath(params); |
| | | String userNo = wrkMast == null ? null : wrkMast.getUserNo(); |
| | | if (Cools.isEmpty(userNo)) { |
| | | userNo = params.getBatch(); |
| | | } |
| | | if (Cools.isEmpty(userNo)) { |
| | | userNo = "_NO_USER_"; |
| | | } |
| | | return path + "#" + userNo; |
| | | } |
| | | |
| | | /** |
| | | * 将一组业务参数转换成 WCS 批量接口的 tasks 数组。 |
| | | */ |
| | | private List<Map<String, Object>> buildTaskPayloads(List<WorkTaskParams> tasks) { |
| | | List<Map<String, Object>> payloads = new ArrayList<>(); |
| | | for (WorkTaskParams task : tasks) { |
| | | payloads.add(buildTaskPayload(task)); |
| | | } |
| | | return payloads; |
| | | } |
| | | |
| | | /** |
| | | * 组装单条任务的 WCS 请求体。 |
| | | * 只放当前任务类型实际需要的字段;空字段不透传,避免给 WCS 造成歧义。 |
| | | */ |
| | | private Map<String, Object> buildTaskPayload(WorkTaskParams params) { |
| | | Map<String, Object> task = new LinkedHashMap<>(); |
| | | if (!Cools.isEmpty(params.getTaskNo())) { |
| | | task.put("taskNo", params.getTaskNo()); |
| | | } |
| | | if (!Cools.isEmpty(params.getLocNo())) { |
| | | task.put("locNo", params.getLocNo()); |
| | | } |
| | | if (!Cools.isEmpty(params.getSourceLocNo())) { |
| | | task.put("sourceLocNo", params.getSourceLocNo()); |
| | | } |
| | | if (!Cools.isEmpty(params.getSourceStaNo())) { |
| | | task.put("sourceStaNo", params.getSourceStaNo()); |
| | | } |
| | | if (!Cools.isEmpty(params.getBarcode())) { |
| | | task.put("barcode", params.getBarcode()); |
| | | } |
| | | if (!Objects.isNull(params.getTaskPri())) { |
| | | task.put("taskPri", params.getTaskPri()); |
| | | } |
| | | if (!Cools.isEmpty(params.getStaNo())) { |
| | | task.put("staNo", params.getStaNo()); |
| | | } |
| | | if (!Cools.isEmpty(params.getBatch())) { |
| | | task.put("batch", params.getBatch()); |
| | | } |
| | | if (!Objects.isNull(params.getBatchSeq())) { |
| | | task.put("batchSeq", params.getBatchSeq()); |
| | | } |
| | | return task; |
| | | } |
| | | |
| | | /** |
| | | * 构造跳过/失败信息时统一带上 taskNo,便于排查具体是哪一条工作档未被下发。 |
| | | */ |
| | | private String buildTaskMsg(WorkTaskParams params, String msg) { |
| | | if (params == null || Cools.isEmpty(params.getTaskNo())) { |
| | | return msg; |
| | | } |
| | | return "taskNo=" + params.getTaskNo() + ", msg=" + msg; |
| | | } |
| | | |
| | | @Override |
| | | public R pauseOutTasks(StopOutTaskParams params) { |
| | | if (params == null || params.getTasks() == null || params.getTasks().isEmpty()) { |
| | | return R.ok("no tasks to stop"); |
| | | public R pauseOutTasks(List<HashMap<String,Object>> params) { |
| | | if (params == null || params.size() == 0) { |
| | | return R.ok("无任务需要取消"); |
| | | } |
| | | if (!Boolean.parseBoolean(String.valueOf(switchValue))) { |
| | | return R.ok("wcs switch off"); |
| | | return R.ok("WCS开关关闭"); |
| | | } |
| | | String response; |
| | | try { |
| | | log.info("调用WCS取消出库任务, request={}", JSON.toJSONString(params)); |
| | | response = new HttpHandler.Builder() |
| | | .setUri(wcs_address) |
| | | .setPath(stopOutTask) |
| | |
| | | .build() |
| | | .doPost(); |
| | | JSONObject jsonObject = JSON.parseObject(response == null ? "{}" : response); |
| | | log.info("WCS取消出库任务返回, response={}", response); |
| | | Integer code = jsonObject.getInteger("code"); |
| | | if (code == null || !Objects.equals(code, 200)) { |
| | | String msg = jsonObject.getString("msg"); |
| | | throw new CoolException(Cools.isEmpty(msg) ? "WCS stop out task failed" : msg); |
| | | throw new CoolException(Cools.isEmpty(msg) ? "WCS取消出库任务失败" : msg); |
| | | } |
| | | return R.ok(Cools.isEmpty(jsonObject.getString("msg")) ? "操作成功" : jsonObject.getString("msg")); |
| | | } catch (IOException e) { |
| | | throw new CoolException("call WCS stop out task fail: " + e.getMessage()); |
| | | throw new CoolException("调用WCS取消出库任务失败: " + e.getMessage()); |
| | | } |
| | | return R.ok(); |
| | | } |
| | | |
| | | } |