| | |
| | | package com.zy.api.service.impl; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.mapper.EntityWrapper; |
| | | import com.core.common.Cools; |
| | |
| | | import com.zy.api.controller.params.ReceviceTaskParams; |
| | | import com.zy.api.controller.params.StopOutTaskParams; |
| | | import com.zy.api.controller.params.WorkTaskParams; |
| | | import com.zy.api.entity.CrnProtocol; |
| | | import com.zy.api.entity.DeviceStatusVo; |
| | | import com.zy.api.entity.StationProtocol; |
| | | import com.zy.api.service.WcsApiService; |
| | | import com.zy.asrs.entity.*; |
| | | import com.zy.asrs.service.*; |
| | |
| | | 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; |
| | | |
| | | @Slf4j |
| | | @Service |
| | | public class WcsApiServiceImpl implements WcsApiService { |
| | | private static final Long WCS_SYNC_USER = 9999L; |
| | | private static final String YES = "Y"; |
| | | private static final String NO = "N"; |
| | | |
| | | /** 同一 WCS 路径、同一单号下一组下发的任务条数上限 */ |
| | | private static final int WCS_PUB_BATCH_SIZE = 20; |
| | | |
| | | /** 三方接口统计:本系统调用 WCS 的 namespace 约定 */ |
| | | private static final String NS_WMS_TO_WCS = "本系统请求WCS"; |
| | | |
| | | @Autowired |
| | | private LocMastService locMastService; |
| | | @Autowired |
| | | private WrkMastService wrkMastService; |
| | | @Autowired |
| | | private WrkMastLogService wrkMastLogService; |
| | | @Autowired |
| | | private WorkService workService; |
| | | @Autowired |
| | |
| | | |
| | | @Value("${wcs.address.stopOutTask}") |
| | | private String stopOutTask; |
| | | @Value("${wcs.address.getDeviceStatus:/openapi/getDeviceStatus}") |
| | | private String getDeviceStatus; |
| | | @Value("${wcs.address.queryTask:/openapi/queryTask}") |
| | | private String queryTaskPath; |
| | | @Value("${wcs.status-sync.method:GET}") |
| | | private String deviceStatusMethod; |
| | | @Autowired |
| | | private CommonService commonService; |
| | | @Autowired |
| | | private BasDevpService basDevpService; |
| | | @Autowired |
| | | private BasCrnpService basCrnpService; |
| | | @Autowired |
| | | private ApiLogService apiLogService; |
| | | |
| | | |
| | | /** |
| | |
| | | */ |
| | | @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("托盘码不能为空!!"); |
| | | } |
| | | 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 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"); |
| | | String validateMsg = validatePubTask(params, wrkMast); |
| | | if (!Cools.isEmpty(validateMsg)) { |
| | | return R.error(validateMsg); |
| | | } |
| | | String url = resolveTaskPath(params); |
| | | String requestJson = JSON.toJSONString(params); |
| | | String response = null; |
| | | R r = R.ok(); |
| | | Throwable wcsThrown = null; |
| | | boolean wcsBizOk = false; |
| | | try { |
| | | log.info("下发搬运任务给wcs="+JSON.toJSONString(params)); |
| | | response = new HttpHandler.Builder() |
| | | .setUri(wcs_address) |
| | | .setPath(url) |
| | | .setJson(JSON.toJSONString(params)) |
| | | .setHttps(wcs_address != null && wcs_address.startsWith("https://")) |
| | | .setTimeout(10, TimeUnit.SECONDS) |
| | | .setJson(requestJson) |
| | | .build() |
| | | .doPost(); |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | log.info("下发任务给wcs的返回值="+response); |
| | | Integer code = jsonObject.getInteger("code"); |
| | | wcsBizOk = code != null && code == 200; |
| | | |
| | | 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(); |
| | | } |
| | | } catch (IOException e) { |
| | | wcsThrown = e; |
| | | throw new RuntimeException(e); |
| | | } finally { |
| | | logWcsToApiLog(url, requestJson, response, wcsThrown, wcsBizOk); |
| | | } |
| | | return r; |
| | | } |
| | | |
| | | @Override |
| | | public R pubWrksToWcs(List<WorkTaskParams> paramsList) { |
| | | if (paramsList == null || paramsList.isEmpty()) { |
| | | return R.error("任务不能为空!!"); |
| | | } |
| | | |
| | | Map<String, WrkMast> wrkMastMap = getWrkMastMap(paramsList); |
| | | List<WorkTaskParams> accepted = new ArrayList<>(); |
| | | 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; |
| | | } |
| | | accepted.add(params); |
| | | } |
| | | |
| | | if (accepted.isEmpty()) { |
| | | return R.error(skipMsgs.isEmpty() ? "无可下发任务" : skipMsgs.get(0)).add(skipMsgs); |
| | | } |
| | | |
| | | accepted = filterOutboundByContiguousPlt(accepted, wrkMastMap, skipMsgs); |
| | | if (accepted.isEmpty()) { |
| | | return R.error(skipMsgs.isEmpty() ? "无可下发任务" : skipMsgs.get(0)).add(skipMsgs); |
| | | } |
| | | |
| | | accepted.sort(pubWcsSortComparator(wrkMastMap)); |
| | | List<List<WorkTaskParams>> chunks = buildPubChunks(accepted, wrkMastMap); |
| | | |
| | | int successCount = 0; |
| | | List<String> failMsgs = new ArrayList<>(); |
| | | List<WorkTaskParams> lastSentChunk = null; |
| | | String skipGroupKey = null; |
| | | |
| | | for (List<WorkTaskParams> chunk : chunks) { |
| | | if (chunk == null || chunk.isEmpty()) { |
| | | continue; |
| | | } |
| | | WorkTaskParams head = chunk.get(0); |
| | | WrkMast headMast = wrkMastMap.get(head.getTaskNo()); |
| | | String key = buildBatchGroupKey(head, headMast); |
| | | |
| | | if (skipGroupKey != null && skipGroupKey.equals(key)) { |
| | | continue; |
| | | } |
| | | |
| | | if (!outboundChunkPredecessorPltReady(chunk, wrkMastMap)) { |
| | | skipGroupKey = key; |
| | | continue; |
| | | } |
| | | |
| | | if (lastSentChunk != null) { |
| | | WorkTaskParams lastHead = lastSentChunk.get(0); |
| | | String lastKey = buildBatchGroupKey(lastHead, wrkMastMap.get(lastHead.getTaskNo())); |
| | | if (lastKey.equals(key)) { |
| | | if (!sameOrderNextChunkAllowed(lastSentChunk)) { |
| | | skipGroupKey = key; |
| | | continue; |
| | | } |
| | | } |
| | | if (!sleepOneMinuteBeforeNextChunk()) { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | int ok = postWcsBatchChunk(chunk, wrkMastMap, failMsgs); |
| | | if (ok <= 0) { |
| | | skipGroupKey = key; |
| | | continue; |
| | | } |
| | | successCount += ok; |
| | | lastSentChunk = chunk; |
| | | } |
| | | |
| | | 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); |
| | | } |
| | | |
| | | private int postWcsBatchChunk(List<WorkTaskParams> chunk, Map<String, WrkMast> wrkMastMap, List<String> failMsgs) { |
| | | if (chunk == null || chunk.isEmpty()) { |
| | | return 0; |
| | | } |
| | | String path = resolveTaskPath(chunk.get(0)); |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | payload.put("taskList", buildTaskPayloads(chunk)); |
| | | String requestJson = JSON.toJSONString(payload); |
| | | String response = null; |
| | | Throwable wcsThrown = null; |
| | | boolean wcsBizOk = false; |
| | | try { |
| | | log.info("批量下发搬运任务给wcs={}", requestJson); |
| | | response = new HttpHandler.Builder() |
| | | .setUri(wcs_address) |
| | | .setPath(path) |
| | | .setTimeout(60, TimeUnit.SECONDS) |
| | | .setJson(requestJson) |
| | | .build() |
| | | .doPost(); |
| | | JSONObject jsonObject = JSON.parseObject(response == null ? "{}" : response); |
| | | log.info("批量下发任务给wcs的返回值={}", response); |
| | | Integer code = jsonObject.getInteger("code"); |
| | | wcsBizOk = code != null && code == 200; |
| | | if (wcsBizOk) { |
| | | for (WorkTaskParams params : chunk) { |
| | | updateWrkMastAfterPublish(wrkMastMap.get(params.getTaskNo())); |
| | | } |
| | | return chunk.size(); |
| | | } |
| | | String msg = jsonObject.getString("msg"); |
| | | failMsgs.add("path=" + path + ", msg=" + (Cools.isEmpty(msg) ? "WCS下发任务失败" : msg)); |
| | | log.error("批量下发任务给wcs失败, path:{}, request:{}, response:{}", path, requestJson, response); |
| | | } catch (IOException e) { |
| | | wcsThrown = e; |
| | | failMsgs.add("path=" + path + ", msg=" + e.getMessage()); |
| | | log.error("批量下发任务给wcs异常, path:{}, request:{}, response:{}", path, requestJson, response, e); |
| | | } finally { |
| | | logWcsToApiLog(path, requestJson, response, wcsThrown, wcsBizOk); |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * 出库:仅当单号、序号均有效时做跳号校验;单号空或序号无效仍下发。入库/移库不处理。 |
| | | */ |
| | | private List<WorkTaskParams> filterOutboundByContiguousPlt(List<WorkTaskParams> accepted, Map<String, WrkMast> wrkMastMap, List<String> skipMsgs) { |
| | | Map<String, Integer> reachCache = new HashMap<>(); |
| | | List<WorkTaskParams> kept = new ArrayList<>(); |
| | | for (WorkTaskParams p : accepted) { |
| | | if (!"out".equalsIgnoreCase(p.getType())) { |
| | | kept.add(p); |
| | | continue; |
| | | } |
| | | WrkMast w = wrkMastMap.get(p.getTaskNo()); |
| | | String userNo = sortUserNoForPub(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); |
| | | if (plt > maxReach) { |
| | | skipMsgs.add(buildTaskMsg(p, "出库序号跳号,跳过")); |
| | | continue; |
| | | } |
| | | kept.add(p); |
| | | } |
| | | return kept; |
| | | } |
| | | |
| | | private List<List<WorkTaskParams>> buildPubChunks(List<WorkTaskParams> accepted, Map<String, WrkMast> wrkMastMap) { |
| | | List<List<WorkTaskParams>> chunks = new ArrayList<>(); |
| | | int index = 0; |
| | | while (index < accepted.size()) { |
| | | WorkTaskParams head = accepted.get(index); |
| | | WrkMast headMast = wrkMastMap.get(head.getTaskNo()); |
| | | String headGroupKey = buildBatchGroupKey(head, headMast); |
| | | List<WorkTaskParams> chunk = new ArrayList<>(); |
| | | while (index < accepted.size() && chunk.size() < WCS_PUB_BATCH_SIZE) { |
| | | WorkTaskParams cur = accepted.get(index); |
| | | WrkMast curMast = wrkMastMap.get(cur.getTaskNo()); |
| | | if (!headGroupKey.equals(buildBatchGroupKey(cur, curMast))) { |
| | | break; |
| | | } |
| | | chunk.add(cur); |
| | | index++; |
| | | } |
| | | chunks.add(chunk); |
| | | } |
| | | return chunks; |
| | | } |
| | | |
| | | private boolean sleepOneMinuteBeforeNextChunk() { |
| | | try { |
| | | TimeUnit.MINUTES.sleep(1); |
| | | return true; |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | log.warn("批量下发WCS组间等待被中断", e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 同单下一组:优先 WCS queryTask;失败或无数据则主表已非 11 或已进历史表。 |
| | | */ |
| | | private boolean sameOrderNextChunkAllowed(List<WorkTaskParams> lastSentChunk) { |
| | | if (lastSentChunk == null || lastSentChunk.isEmpty()) { |
| | | return false; |
| | | } |
| | | if (!Boolean.parseBoolean(String.valueOf(switchValue))) { |
| | | return true; |
| | | } |
| | | WorkTaskParams last = lastSentChunk.get(lastSentChunk.size() - 1); |
| | | if (last != null && !Cools.isEmpty(last.getTaskNo()) && wcsQueryTaskShowsTask(last.getTaskNo())) { |
| | | return true; |
| | | } |
| | | if (last != null && !Cools.isEmpty(last.getTaskNo())) { |
| | | log.info("WCS queryTask 无数据或失败,回退 WMS 主表/历史校验, taskNo={}", last.getTaskNo()); |
| | | } |
| | | return previousChunkTasksReleasedInWms(lastSentChunk); |
| | | } |
| | | |
| | | private boolean wcsQueryTaskShowsTask(String taskNo) { |
| | | Map<String, Object> body = new HashMap<>(); |
| | | body.put("taskNo", taskNo); |
| | | try { |
| | | String response = new HttpHandler.Builder() |
| | | .setUri(wcs_address) |
| | | .setPath(queryTaskPath) |
| | | .setHttps(wcs_address != null && wcs_address.startsWith("https://")) |
| | | .setTimeout(60, TimeUnit.SECONDS) |
| | | .setJson(JSON.toJSONString(body)) |
| | | .build() |
| | | .doPost(); |
| | | JSONObject jo = JSON.parseObject(response == null ? "{}" : response); |
| | | Integer code = jo.getInteger("code"); |
| | | return code != null && code == 200 && queryTaskDataNonEmpty(jo.get("data")); |
| | | } catch (IOException e) { |
| | | log.warn("WCS queryTask 异常, taskNo={}", taskNo, e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 上一组每条:主表无则看历史表;主表有则 wrk_sts 不能仍为 11。 |
| | | */ |
| | | private boolean previousChunkTasksReleasedInWms(List<WorkTaskParams> chunk) { |
| | | for (WorkTaskParams p : chunk) { |
| | | if (p == null || Cools.isEmpty(p.getTaskNo())) { |
| | | return false; |
| | | } |
| | | Integer wrkNo = Integer.valueOf(p.getTaskNo()); |
| | | WrkMast m = wrkMastService.selectById(wrkNo); |
| | | if (m == null) { |
| | | int logCnt = wrkMastLogService.selectCount(new EntityWrapper<WrkMastLog>().eq("wrk_no", wrkNo)); |
| | | if (logCnt <= 0) { |
| | | return false; |
| | | } |
| | | } else if (m.getWrkSts() != null && Objects.equals(m.getWrkSts(), 11L)) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 出库每组下发前:本组有有效最小序号且>1 时,只校验「最小序号-1」一档;序号全无则跳过本条件。 |
| | | */ |
| | | private boolean outboundChunkPredecessorPltReady(List<WorkTaskParams> chunk, Map<String, WrkMast> wrkMastMap) { |
| | | if (chunk == null || chunk.isEmpty()) { |
| | | return true; |
| | | } |
| | | WorkTaskParams head = chunk.get(0); |
| | | if (!"out".equalsIgnoreCase(head.getType())) { |
| | | return true; |
| | | } |
| | | WrkMast headMast = wrkMastMap.get(head.getTaskNo()); |
| | | String userNo = sortUserNoForPub(head, headMast); |
| | | if (Cools.isEmpty(userNo)) { |
| | | return true; |
| | | } |
| | | int minPlt = Integer.MAX_VALUE; |
| | | for (WorkTaskParams p : chunk) { |
| | | if (!"out".equalsIgnoreCase(p.getType())) { |
| | | continue; |
| | | } |
| | | Integer plt = sortPltForPub(p, wrkMastMap.get(p.getTaskNo())); |
| | | if (plt != null && plt > 0 && plt < minPlt) { |
| | | minPlt = plt; |
| | | } |
| | | } |
| | | if (minPlt == Integer.MAX_VALUE || minPlt <= 1) { |
| | | return true; |
| | | } |
| | | return outboundPltSlotReleasedInWms(userNo, 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)); |
| | | if (rows != null && !rows.isEmpty()) { |
| | | for (WrkMast m : rows) { |
| | | if (m != null && m.getWrkSts() != null && Objects.equals(m.getWrkSts(), 11L)) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | int logCnt = wrkMastLogService.selectCount(new EntityWrapper<WrkMastLog>() |
| | | .eq("user_no", userNo) |
| | | .eq("io_type", 101) |
| | | .eq("plt_type", pltType)); |
| | | return logCnt > 0; |
| | | } |
| | | |
| | | private static boolean queryTaskDataNonEmpty(Object data) { |
| | | if (data == null) { |
| | | return false; |
| | | } |
| | | if (data instanceof JSONArray) { |
| | | return !((JSONArray) data).isEmpty(); |
| | | } |
| | | if (data instanceof Collection) { |
| | | return !((Collection<?>) data).isEmpty(); |
| | | } |
| | | if (data instanceof String) { |
| | | String s = (String) data; |
| | | if (Cools.isEmpty(s)) { |
| | | return false; |
| | | } |
| | | JSONArray arr = JSON.parseArray(s); |
| | | return arr != null && !arr.isEmpty(); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private Comparator<WorkTaskParams> pubWcsSortComparator(Map<String, WrkMast> wrkMastMap) { |
| | | 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 -> sortPltForPub(p, wrkMastMap.get(p.getTaskNo())), Comparator.nullsLast(Integer::compareTo)); |
| | | } |
| | | |
| | | private static String sortUserNoForPub(WorkTaskParams p, WrkMast wrkMast) { |
| | | String userNo = wrkMast == null ? null : wrkMast.getUserNo(); |
| | | if (Cools.isEmpty(userNo)) { |
| | | userNo = p.getBatch(); |
| | | } |
| | | return Cools.isEmpty(userNo) ? null : userNo; |
| | | } |
| | | |
| | | private static Integer sortPltForPub(WorkTaskParams p, WrkMast wrkMast) { |
| | | if (wrkMast != null && wrkMast.getPltType() != null) { |
| | | return wrkMast.getPltType(); |
| | | } |
| | | return p.getBatchSeq(); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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(); |
| | | } |
| | | |
| | | @Override |
| | | public R pauseOutTasks(StopOutTaskParams params) { |
| | | if (params == null || params.getTasks() == null || params.getTasks().isEmpty()) { |
| | | return R.ok("no tasks to stop"); |
| | | public R syncDeviceStatusFromWcs(boolean logOnFailure) { |
| | | if (!Boolean.parseBoolean(String.valueOf(switchValue))) { |
| | | return R.ok("WCS开关关闭"); |
| | | } |
| | | String response = null; |
| | | try { |
| | | response = requestDeviceStatusFromWcs(); |
| | | JSONObject jsonObject = JSON.parseObject(response == null ? "{}" : response); |
| | | Integer code = jsonObject.getInteger("code"); |
| | | if (!Objects.equals(code, 200)) { |
| | | String msg = jsonObject.getString("msg"); |
| | | return R.error(Cools.isEmpty(msg) ? "获取WCS设备状态失败" : msg); |
| | | } |
| | | JSONObject data = jsonObject.getJSONObject("data"); |
| | | DeviceStatusVo deviceStatusVo = data == null |
| | | ? new DeviceStatusVo() |
| | | : JSON.parseObject(data.toJSONString(), DeviceStatusVo.class); |
| | | |
| | | int stationCount = syncStationStatus(deviceStatusVo.getStationList()); |
| | | int crnCount = syncCrnStatus(deviceStatusVo.getCrnList()); |
| | | |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("stationCount", stationCount); |
| | | result.put("crnCount", crnCount); |
| | | log.info("同步WCS设备状态成功, stationCount={}, crnCount={}", stationCount, crnCount); |
| | | return R.ok("同步成功").add(result); |
| | | } catch (Exception e) { |
| | | if (logOnFailure) { |
| | | log.error("同步WCS设备状态异常, response={}", response, e); |
| | | } else { |
| | | log.debug("同步WCS设备状态异常, response={}", response, e); |
| | | } |
| | | return R.error("同步WCS设备状态失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | 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(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; |
| | | HashMap<String,Object> map = new HashMap<>(); |
| | | map.put("taskList", params); |
| | | String requestJson = JSON.toJSONString(map); |
| | | String response = null; |
| | | Throwable wcsThrown = null; |
| | | boolean wcsBizOk = false; |
| | | try { |
| | | log.info("调用WCS取消出库任务, request={}", requestJson); |
| | | response = new HttpHandler.Builder() |
| | | .setUri(wcs_address) |
| | | .setPath(stopOutTask) |
| | | .setHttps(wcs_address != null && wcs_address.startsWith("https://")) |
| | | // .setHttps(wcs_address != null && wcs_address.startsWith("https://")) |
| | | .setTimeout(10, TimeUnit.SECONDS) |
| | | .setJson(JSON.toJSONString(params)) |
| | | .setJson(requestJson) |
| | | .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)) { |
| | | wcsBizOk = code != null && Objects.equals(code, 200); |
| | | if (!wcsBizOk) { |
| | | 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()); |
| | | wcsThrown = e; |
| | | throw new CoolException("调用WCS取消出库任务失败: " + e.getMessage()); |
| | | } finally { |
| | | logWcsToApiLog(stopOutTask, requestJson, response, wcsThrown, wcsBizOk); |
| | | } |
| | | return R.ok(); |
| | | } |
| | | |
| | | private void logWcsToApiLog(String path, String requestJson, String response, Throwable thrown, boolean wcsBizOk) { |
| | | String fullUrl = (wcs_address == null ? "" : wcs_address) + (path == null ? "" : path); |
| | | boolean success = thrown == null && wcsBizOk; |
| | | String resp = response == null ? "" : response; |
| | | if (thrown != null && Cools.isEmpty(resp)) { |
| | | resp = thrown.getMessage() == null ? "" : thrown.getMessage(); |
| | | } |
| | | apiLogService.save(NS_WMS_TO_WCS, fullUrl, "-", "-", |
| | | requestJson == null ? "" : requestJson, resp, success); |
| | | } |
| | | |
| | | private String requestDeviceStatusFromWcs() throws IOException { |
| | | HttpHandler.Builder builder = new HttpHandler.Builder() |
| | | .setUri(wcs_address) |
| | | .setPath(getDeviceStatus) |
| | | .setTimeout(10, TimeUnit.SECONDS); |
| | | String method = Cools.isEmpty(deviceStatusMethod) ? "POST" : deviceStatusMethod.trim().toUpperCase(Locale.ROOT); |
| | | if ("POST".equals(method)) { |
| | | return builder.setJson("{}").build().doPost(); |
| | | } |
| | | return builder.build().doGet(); |
| | | } |
| | | |
| | | private int syncStationStatus(List<StationProtocol> stationList) { |
| | | if (stationList == null || stationList.isEmpty()) { |
| | | return 0; |
| | | } |
| | | int count = 0; |
| | | Date now = new Date(); |
| | | for (StationProtocol stationProtocol : stationList) { |
| | | if (stationProtocol == null || stationProtocol.getStationId() == null) { |
| | | continue; |
| | | } |
| | | BasDevp basDevp = basDevpService.selectById(stationProtocol.getStationId()); |
| | | boolean isNew = Objects.isNull(basDevp); |
| | | if (isNew) { |
| | | basDevp = new BasDevp(); |
| | | basDevp.setDevNo(stationProtocol.getStationId()); |
| | | basDevp.setAppeUser(WCS_SYNC_USER); |
| | | basDevp.setAppeTime(now); |
| | | } |
| | | basDevp.setInEnable(toFlag(stationProtocol.isInEnable())); |
| | | basDevp.setOutEnable(toFlag(stationProtocol.isOutEnable())); |
| | | basDevp.setAutoing(toFlag(stationProtocol.isAutoing())); |
| | | basDevp.setLoading(toFlag(stationProtocol.isLoading())); |
| | | basDevp.setCanining(toFlag(stationProtocol.isEnableIn())); |
| | | basDevp.setCanouting(toFlag(!stationProtocol.isRunBlock())); |
| | | basDevp.setWrkNo(defaultZero(stationProtocol.getTaskNo())); |
| | | basDevp.setBarcode(normalizeText(stationProtocol.getBarcode())); |
| | | basDevp.setGrossWt(stationProtocol.getWeight() == null ? 0D : stationProtocol.getWeight()); |
| | | basDevp.setModiUser(WCS_SYNC_USER); |
| | | basDevp.setModiTime(now); |
| | | if (isNew) { |
| | | if (!basDevpService.insert(basDevp)) { |
| | | throw new CoolException("新增站点状态失败, stationId=" + stationProtocol.getStationId()); |
| | | } |
| | | } else if (!basDevpService.updateById(basDevp)) { |
| | | throw new CoolException("更新站点状态失败, stationId=" + stationProtocol.getStationId()); |
| | | } |
| | | count++; |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | private int syncCrnStatus(List<CrnProtocol> crnList) { |
| | | if (crnList == null || crnList.isEmpty()) { |
| | | return 0; |
| | | } |
| | | int count = 0; |
| | | Date now = new Date(); |
| | | for (CrnProtocol crnProtocol : crnList) { |
| | | if (crnProtocol == null || crnProtocol.getCrnNo() == null) { |
| | | continue; |
| | | } |
| | | BasCrnp basCrnp = basCrnpService.selectById(crnProtocol.getCrnNo()); |
| | | boolean isNew = Objects.isNull(basCrnp); |
| | | if (isNew) { |
| | | basCrnp = new BasCrnp(); |
| | | basCrnp.setCrnNo(crnProtocol.getCrnNo()); |
| | | basCrnp.setInEnable(YES); |
| | | basCrnp.setOutEnable(YES); |
| | | basCrnp.setAppeUser(WCS_SYNC_USER); |
| | | basCrnp.setAppeTime(now); |
| | | } |
| | | // crn_sts 本地表存的是“堆垛机模式(手动/自动/电脑)”,因此必须写 mode,不能写 status。 |
| | | basCrnp.setCrnSts(defaultZero(crnProtocol.getMode())); |
| | | basCrnp.setWrkNo(defaultZero(crnProtocol.getTaskNo())); |
| | | basCrnp.setCrnErr(crnProtocol.getAlarm() == null ? 0L : Long.valueOf(crnProtocol.getAlarm())); |
| | | basCrnp.setModiUser(WCS_SYNC_USER); |
| | | basCrnp.setModiTime(now); |
| | | if (isNew) { |
| | | if (!basCrnpService.insert(basCrnp)) { |
| | | throw new CoolException("新增堆垛机状态失败, crnNo=" + crnProtocol.getCrnNo()); |
| | | } |
| | | } else if (!basCrnpService.updateById(basCrnp)) { |
| | | throw new CoolException("更新堆垛机状态失败, crnNo=" + crnProtocol.getCrnNo()); |
| | | } |
| | | count++; |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | private Integer defaultZero(Integer value) { |
| | | return value == null ? 0 : value; |
| | | } |
| | | |
| | | private String normalizeText(String value) { |
| | | return Cools.isEmpty(value) ? "" : value; |
| | | } |
| | | |
| | | private String toFlag(boolean value) { |
| | | return value ? YES : NO; |
| | | } |
| | | |
| | | } |