package com.zy.asrs.service.impl; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.core.common.Cools; import com.core.common.R; import com.zy.asrs.entity.param.MesToCombParam; import com.zy.asrs.entity.param.OpenOrderPakoutExecuteParam; import com.zy.asrs.entity.param.OutTaskParam; import com.zy.asrs.service.ExternalTaskFacadeService; import com.zy.asrs.service.LocDetlService; import com.zy.asrs.service.OpenService; import com.zy.asrs.service.WaitPakinService; import com.zy.asrs.service.WrkDetlService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Collections; import java.util.Objects; /** * 对外任务门面。 * 把 Controller 里的校验和放行逻辑沉到 service,便于 HTTP 和 MQTT 两条入口共用。 */ @Service public class ExternalTaskFacadeServiceImpl implements ExternalTaskFacadeService { @Autowired private OpenService openService; @Autowired private LocDetlService locDetlService; @Autowired private WrkDetlService wrkDetlService; @Autowired private WaitPakinService waitPakinService; /** * 复用现有入库通知建档逻辑,并补充托盘重复校验。 */ @Override public R acceptInboundNotice(MesToCombParam param) { if (param == null) { return R.error("请求参数不能为空"); } if (Cools.isEmpty(param.getPalletId())) { return R.error("palletId不能为空"); } if (Cools.isEmpty(param.getBizNo())) { return R.error("bizNo不能为空"); } int countLoc = locDetlService.selectCount(new EntityWrapper().eq("zpallet", param.getPalletId())); int countWrk = wrkDetlService.selectCount(new EntityWrapper().eq("zpallet", param.getPalletId())); if (countLoc > 0 || countWrk > 0) { return R.error("托盘已在库存中/已开始入库"); } if (waitPakinService.selectCount(new EntityWrapper() .eq("zpallet", param.getPalletId()) .eq("io_status", "N")) > 0) { // 同托盘存在旧的待入库通知时,删除旧记录,保留最新一次预登记。 waitPakinService.delete(new EntityWrapper().eq("zpallet", param.getPalletId())); } R result = openService.mesToComb(param); return result == null ? R.ok() : result; } /** * 复用统一出库订单化逻辑。 * * MQTT/IoT 入口不再绕过订单直接生成 WrkMast: * - autoConfirm=false:只创建 status=0 的订单,等待外部调用执行接口; * - autoConfirm=true:创建订单后立即走执行逻辑,生成当前批次任务并确认可下发。 * * 这样做是为了让 HTTP /outOrder 和 MQTT/IoT 直调共享同一套: * - 订单明细保存字段; * - 批次键计算; * - 中止取消和 work_qty 回滚; * - pdcType 放行。 */ @Override public R createOutboundTask(OutTaskParam param, boolean autoConfirm) { if (param == null) { return R.error("请求参数不能为空"); } if (Cools.isEmpty(param.getOrderId())) { return R.error("出库单号不能为空"); } if (Cools.isEmpty(param.getPalletId())) { return R.error("palletId不能为空"); } if (Cools.isEmpty(param.getStationId())) { return R.error("stationId不能为空"); } int countLoc = locDetlService.selectCount(new EntityWrapper().eq("zpallet", param.getPalletId())); if (countLoc == 0) { return R.error("库存中不存在该托盘:" + param.getPalletId()); } if (param.getSeq() == null) { // 设备直调通常是单托盘出库,没有 ERP 顺序号;0 表示无序,和 /outOrder 的校验语义一致。 param.setSeq(0); } if (Cools.isEmpty(param.getBatchSeq())) { // batchSeq 是接口原始字段,明细里会保存;实际生成任务时低站点仍按 orderId 作为批次键。 param.setBatchSeq(param.getOrderId()); } if (isHighStation(param.getStationId()) && Cools.isEmpty(param.getEntryWmsCode())) { // IoT 直调常见为单托盘任务,没有 ERP 进仓编号;用 orderId 作为批次键, // 这样既满足高站点订单明细校验,也能让执行后 WrkMast.batchSeq 保持可追溯。 param.setEntryWmsCode(param.getOrderId()); } // IoT/MQTT 默认只预创建订单,status=0 不会被定时器扫描。 // 只有 autoConfirm=true 或外部后续调用执行接口时,才会把 status 恢复为 1 并生成任务。 R orderResult = openService.outOrderCreatePakoutOrder(Collections.singletonList(param), false); if (!Objects.equals(orderResult.get("code"), 200) || !autoConfirm) { return orderResult; } // IoT pick 约定为“收到即执行”,因此建单后直接复用公开执行接口的服务逻辑。 OpenOrderPakoutExecuteParam executeParam = new OpenOrderPakoutExecuteParam(); executeParam.setOrderId(param.getOrderId()); executeParam.setExecute(1); return openService.pakoutOrderExecute(executeParam); } private boolean isHighStation(String stationId) { if (Cools.isEmpty(stationId)) { return false; } try { return Integer.valueOf(stationId) > 600; } catch (NumberFormatException ignored) { return false; } } }