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.core.common.R;
|
import com.core.exception.CoolException;
|
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 com.zy.asrs.utils.Utils;
|
import com.zy.common.constant.MesConstant;
|
import com.zy.common.service.CommonService;
|
import com.zy.common.utils.HttpHandler;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.beans.BeanUtils;
|
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.io.IOException;
|
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
|
private WrkDetlService wrkDetlService;
|
@Autowired
|
private MatService matService;
|
@Autowired
|
private LocDetlService locDetlService;
|
@Value("${wcs.switch}")
|
private String switchValue;
|
|
@Value("${wcs.address.URL}")
|
private String wcs_address;
|
|
@Value("${wcs.address.createOutTask}")
|
private String getWcs_address;
|
|
@Value("${wcs.address.createInTask}")
|
private String createInTask;
|
|
@Value("${wcs.address.createLocMoveTask}")
|
private String createLocMoveTask;
|
|
@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;
|
|
|
/**
|
* 下发任务至WCS
|
*
|
* @param params
|
* @return com.core.common.R
|
* @author Ryan
|
* @date 2026/1/10 13:58
|
*/
|
@Override
|
public R pubWrkToWcs(WorkTaskParams params) {
|
if (params == null) {
|
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);
|
}
|
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)
|
.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) {
|
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();
|
}
|
|
/**
|
* 堆垛机执行状态上报
|
*
|
* @param params
|
* @return com.core.common.R
|
* @author Ryan
|
* @date 2026/1/10 16:30
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public R receviceTaskFromWcs(ReceviceTaskParams params) {
|
log.info("wcs任务反馈="+JSON.toJSONString(params));
|
if (Objects.isNull(params.getSuperTaskNo())) {
|
throw new CoolException("WMS任务号不能为空!!");
|
}
|
if (Objects.isNull(params.getNotifyType())) {
|
throw new CoolException("动作类型不能为空!!");
|
}
|
WrkMast mast = wrkMastService.selectOne(new EntityWrapper<WrkMast>().eq("wrk_no", params.getSuperTaskNo()));
|
if (Objects.isNull(mast)) {
|
throw new CoolException("任务档不存在!!");
|
}
|
|
|
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) {
|
mast.setWrkSts(14L);
|
if(Cools.isEmpty(mast.getStaNo())){
|
mast.setOveMk("Y");
|
}
|
}
|
if (!wrkMastService.updateById(mast)) {
|
throw new CoolException("任务状态修改失败!!");
|
}
|
//wcs任务取消接口
|
} else if (params.getMsgType().equals("task_cancel")) {
|
workService.cancelWrkMast(String.valueOf(mast.getWrkNo()), 9955L);
|
} else if (params.getMsgType().equals("task_arrive")) {
|
//到达目的地
|
//如果出库任务是跨区则需要生成新的入库任务入库
|
if(!Cools.isEmpty(mast.getLocNo())){
|
mast.setOnlineYn("N");//等待生成跨区入库任务
|
}
|
mast.setWrkSts(14L);
|
if(Cools.isEmpty(mast.getStaNo())){
|
mast.setOveMk("Y");
|
}
|
mast.setModiTime(new Date());
|
if (!wrkMastService.updateById(mast)) {
|
throw new CoolException("任务状态修改失败!!");
|
}
|
}
|
} else if (params.getNotifyType().equals("weight")) {
|
|
}
|
return R.ok();
|
}
|
|
@Override
|
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开关关闭");
|
}
|
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://"))
|
.setTimeout(10, TimeUnit.SECONDS)
|
.setJson(requestJson)
|
.build()
|
.doPost();
|
JSONObject jsonObject = JSON.parseObject(response == null ? "{}" : response);
|
log.info("WCS取消出库任务返回, response={}", response);
|
Integer code = jsonObject.getInteger("code");
|
wcsBizOk = code != null && Objects.equals(code, 200);
|
if (!wcsBizOk) {
|
String msg = jsonObject.getString("msg");
|
throw new CoolException(Cools.isEmpty(msg) ? "WCS取消出库任务失败" : msg);
|
}
|
return R.ok(Cools.isEmpty(jsonObject.getString("msg")) ? "操作成功" : jsonObject.getString("msg"));
|
} catch (IOException e) {
|
wcsThrown = e;
|
throw new CoolException("调用WCS取消出库任务失败: " + e.getMessage());
|
} finally {
|
logWcsToApiLog(stopOutTask, requestJson, response, wcsThrown, wcsBizOk);
|
}
|
}
|
|
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;
|
}
|
|
}
|