package com.zy.api.service.impl;
|
|
import com.alibaba.fastjson.JSON;
|
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.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 {
|
|
@Autowired
|
private LocMastService locMastService;
|
@Autowired
|
private WrkMastService wrkMastService;
|
@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;
|
@Autowired
|
private CommonService commonService;
|
|
|
/**
|
* 下发任务至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 response;
|
R r = R.ok();
|
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();
|
JSONObject jsonObject = JSON.parseObject(response);
|
log.info("下发任务给wcs的返回值="+response);
|
Integer code = jsonObject.getInteger("code");
|
|
if (code==200) {
|
updateWrkMastAfterPublish(wrkMast);
|
//TODO 上报是否成功
|
}else {
|
r =R.error();
|
}
|
} catch (IOException e) {
|
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);
|
}
|
|
/**
|
* 堆垛机执行状态上报
|
*
|
* @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();
|
}
|
|
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开关关闭");
|
}
|
String response;
|
try {
|
log.info("调用WCS取消出库任务, request={}", JSON.toJSONString(params));
|
response = new HttpHandler.Builder()
|
.setUri(wcs_address)
|
.setPath(stopOutTask)
|
.setHttps(wcs_address != null && wcs_address.startsWith("https://"))
|
.setTimeout(10, TimeUnit.SECONDS)
|
.setJson(JSON.toJSONString(params))
|
.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取消出库任务失败" : msg);
|
}
|
return R.ok(Cools.isEmpty(jsonObject.getString("msg")) ? "操作成功" : jsonObject.getString("msg"));
|
} catch (IOException e) {
|
throw new CoolException("调用WCS取消出库任务失败: " + e.getMessage());
|
}
|
}
|
|
}
|