自动化立体仓库 - WMS系统
src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java
@@ -33,7 +33,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.rmi.CORBA.Util;
import java.util.*;
import java.util.stream.Collectors;
@@ -88,6 +87,10 @@
    private String mesUrl;
    @Value("${mes.stationaddress}")
    private String stationAddress;
    @Value("${erp.address.URL:}")
    private String erpUrl;
    @Value("${erp.address.OutErroraddress:}")
    private String erpOutErrorAddress;
    @Autowired
    private WaitPakinService waitPakinService;
    @Autowired
@@ -98,6 +101,8 @@
    private WorkService workService;
    @Autowired
    private BasCrnpService basCrnpService;
    @Autowired
    private ApiLogService apiLogService;
    @Override
    @Transactional
@@ -374,77 +379,88 @@
    @Override
    @Transactional
    public R pakoutOrderPause(OpenOrderPakoutPauseParam param) {
        if (param == null || Cools.isEmpty(param.getOrderNo())) {
            throw new CoolException("orderNo is empty");
        if (param == null || Cools.isEmpty(param.getOrderId())) {
            throw new CoolException("orderNo不能为空");
        }
        Order order = orderService.selectByNo(param.getOrderNo());
        if (order == null) {
            order = OrderInAndOutUtil.selectByNo(Boolean.FALSE, param.getOrderNo());
        }
        if (order == null) {
            throw new CoolException("order not found: " + param.getOrderNo());
        if (param.getExecute() == null) {
            throw new CoolException("execute不能为空");
        }
        List<WrkMast> activeTasks = findActiveOutboundTasks(param.getOrderNo());
        if (activeTasks.isEmpty()) {
            return R.ok("no active out tasks");
        }
        Date now = new Date();
        int pausedCount = 0;
        List<WrkMast> issuedTasks = new ArrayList<>();
        for (WrkMast wrkMast : activeTasks) {
            if (wrkMast == null || "Y".equalsIgnoreCase(wrkMast.getPauseMk())) {
                continue;
        List<WrkMast> activeTasks = findActiveOutboundTasks(param.getOrderId());
        if (Objects.equals(param.getExecute(), 1)) {
            // ERP确认立即执行,仅处理待下发的出库任务。
            List<WrkMast> pendingTasks = activeTasks.stream()
                    .filter(wrkMast -> wrkMast != null && Objects.equals(wrkMast.getWrkSts(), 11L))
                    .collect(Collectors.toList());
            Map<String, Object> result = new HashMap<>();
            result.put("orderNo", param.getOrderId());
            result.put("taskCount", pendingTasks.size());
            if (pendingTasks.isEmpty()) {
                result.put("confirmedCount", 0);
                return R.ok("无有效出库任务").add(result);
            }
            wrkMast.setPauseMk("Y");
            wrkMast.setUpdMk(needNotifyWcsStop(wrkMast) ? "WCS_STOP_REQUESTED" : "WMS_PAUSED");
            wrkMast.setManuType("ERP_PAUSE");
            wrkMast.setModiTime(now);
            wrkMast.setModiUser(9527L);
            if (!wrkMastService.updateById(wrkMast)) {
                throw new CoolException("pause out task failed: " + wrkMast.getWrkNo());
            Date now = new Date();
            int confirmedCount = 0;
            for (WrkMast wrkMast : pendingTasks) {
                if (wrkMast == null || "Y".equalsIgnoreCase(wrkMast.getPdcType())) {
                    continue;
                }
                wrkMast.setPdcType("Y");
//            wrkMast.setUpdMk("ERP_CONFIRMED");
//            wrkMast.setManuType("ERP_CONFIRM_OUT");
                wrkMast.setModiTime(now);
                wrkMast.setModiUser(9527L);
                if (!wrkMastService.updateById(wrkMast)) {
                    throw new CoolException("确认执行出库任务失败: " + wrkMast.getWrkNo());
                }
                confirmedCount++;
            }
            pausedCount++;
            if (needNotifyWcsStop(wrkMast)) {
                issuedTasks.add(wrkMast);
            result.put("confirmedCount", confirmedCount);
            return R.ok(confirmedCount == 0 ? "任务已确认执行" : "ERP确认执行出库成功").add(result);
        }
        if (Objects.equals(param.getExecute(), 2)) {
            // ERP请求取消任务:直接收集任务号,按 taskList 格式发送给 WCS。
            Map<String, Object> result = new HashMap<>();
            result.put("orderNo", param.getOrderId());
            result.put("execute", param.getExecute());
            result.put("taskCount", activeTasks.size());
            if (activeTasks.isEmpty()) {
                return R.ok("无有效出库任务").add(result);
            }
            List<HashMap<String,Object>> taskList = new ArrayList<>();
            for (WrkMast wrkMast : activeTasks) {
                HashMap<String,Object> hashMap = new HashMap<>();
                hashMap.put("taskNo", wrkMast.getWrkNo());
                if (!Cools.isEmpty(wrkMast) && wrkMast.getWrkSts() ==11L) {
                    workService.cancelWrkMast(wrkMast.getWrkNo()+"", 9955L);
                    continue;
                }
                taskList.add(hashMap);
            }
            wcsApiService.pauseOutTasks(taskList);
            return R.ok("取消任务已发送至WCS").add(result);
        }
        throw new CoolException("reason仅支持1或2");
    }
        if (pausedCount == 0) {
            return R.ok("tasks already paused");
    /** WCS 返回非成功码时抛错 */
    private void requireWcsPauseOk(R wcsR) {
        if (wcsR == null) {
            throw new CoolException("WCS取消出库任务无返回");
        }
        if (!issuedTasks.isEmpty()) {
            wcsApiService.pauseOutTasks(buildStopOutTaskParams(param, issuedTasks));
        Object codeObj = wcsR.get("code");
        int code = codeObj instanceof Number ? ((Number) codeObj).intValue() : -1;
        if (code != 200) {
            Object msgObj = wcsR.get("msg");
            throw new CoolException(msgObj == null ? "WCS取消出库任务失败" : String.valueOf(msgObj));
        }
        Map<String, Object> result = new HashMap<>();
        result.put("orderNo", param.getOrderNo());
        result.put("pausedCount", pausedCount);
        result.put("wcsStopCount", issuedTasks.size());
        return R.ok("pause out success").add(result);
    }
    @Override
    @Transactional
    public R pakoutOrderExecute(OpenOrderPakoutExecuteParam param) {
        if (param == null || Cools.isEmpty(param.getOrderId())) {
            throw new CoolException("orderId is empty");
        }
        if (param.getExecute() == null) {
            throw new CoolException("execute is empty");
        }
        if (Objects.equals(param.getExecute(), 1)) {
            return createPakoutTasks(param.getOrderId());
        }
        if (Objects.equals(param.getExecute(), 2)) {
            OpenOrderPakoutPauseParam pauseParam = new OpenOrderPakoutPauseParam();
            pauseParam.setOrderNo(param.getOrderId());
            pauseParam.setReason("OPEN_API_PAUSE");
            return pakoutOrderPause(pauseParam);
        }
        throw new CoolException("execute only supports 1 or 2");
        return null;
    }
    private List<WrkMast> findActiveOutboundTasks(String orderNo) {
@@ -461,8 +477,8 @@
        }
        List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                .in("wrk_no", wrkNos)
                .in("io_type", Arrays.asList(101, 103, 107, 108, 110))
                .lt("wrk_sts", 14L));
                .in("io_type", Arrays.asList(101, 103, 104, 107, 108, 110))
                .in("wrk_sts", Arrays.asList(11L, 12L, 13L)));
        if (wrkMasts == null || wrkMasts.isEmpty()) {
            return Collections.emptyList();
        }
@@ -573,24 +589,12 @@
        return orderDetl.getMatnr() + "|batch=" + (orderDetl.getBatch() == null ? "" : orderDetl.getBatch()) + "|lack=" + lackQty;
    }
    private boolean needNotifyWcsStop(WrkMast wrkMast) {
        return wrkMast != null
                && wrkMast.getWrkSts() != null
                && wrkMast.getWrkSts() >= 12L
                && wrkMast.getWrkSts() < 14L;
    }
    private StopOutTaskParams buildStopOutTaskParams(OpenOrderPakoutPauseParam param, List<WrkMast> wrkMasts) {
    private StopOutTaskParams buildStopOutTaskParams(List<WrkMast> wrkMasts) {
        StopOutTaskParams stopParams = new StopOutTaskParams();
        stopParams.setOrderNo(param.getOrderNo());
        stopParams.setReason(param.getReason());
        // WCS取消接口仅接收 taskList.taskNo。
        for (WrkMast wrkMast : wrkMasts) {
            StopOutTaskParams.TaskItem item = new StopOutTaskParams.TaskItem();
            item.setTaskNo(String.valueOf(wrkMast.getWrkNo()));
            if (wrkMast.getStaNo() != null) {
                item.setStaNo(String.valueOf(wrkMast.getStaNo()));
            }
            item.setLocNo(wrkMast.getSourceLocNo());
            stopParams.getTasks().add(item);
        }
        return stopParams;
@@ -1239,7 +1243,10 @@
                stationParams.add(stationParam);
            }
        }
        String requestJson = JSON.toJSONString(stationParams);
        String response = "";
        boolean pushOk = false;
        String pushUrl = buildMesStationRequestUrl();
        try {
            //获取Cookie值
            HashMap<String, Object> headers = new HashMap<>();
@@ -1249,21 +1256,55 @@
                    .setHeaders(headers)
                    .setUri(mesUrl)
                    .setPath(stationAddress)
                    .setJson(JSON.toJSONString(stationParams))
                    .setJson(requestJson)
                    .build()
                    .doPost();
            JSONObject jsonResponse = JSON.parseObject(response);
            if (jsonResponse.getInteger("code") == 200) {
            if (jsonResponse != null && Integer.valueOf(200).equals(jsonResponse.getInteger("code"))) {
                pushOk = true;
            } else {
                return R.error();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                apiLogService.save(
                        "推ERP-站点同步",
                        pushUrl,
                        null,
                        "127.0.0.1",
                        requestJson,
                        response,
                        pushOk,
                        "站点同步推对方"
                );
            } catch (Exception logEx) {
                log.error("save station sync api log failed", logEx);
            }
        }
        return R.ok();
    }
    private String buildMesStationRequestUrl() {
        if (Cools.isEmpty(mesUrl)) {
            return stationAddress;
        }
        if (stationAddress == null) {
            return mesUrl;
        }
        if (mesUrl.endsWith("/") && stationAddress.startsWith("/")) {
            return mesUrl + stationAddress.substring(1);
        }
        if (!mesUrl.endsWith("/") && !stationAddress.startsWith("/")) {
            return mesUrl + "/" + stationAddress;
        }
        return mesUrl + stationAddress;
    }
    /**
     * 7.3 组托信息下发。
     */
    @Override
    public R mesToComb(MesToCombParam param) {
        if (Cools.isEmpty(param.getPalletId())) {
@@ -1276,7 +1317,12 @@
            if (param.getFull() == 1) {
                //满托盘
                mat = matService.selectByMatnr(param.getMatnr());
                if(param.getBoxType1().equals("aws")){
                    mat = matService.selectByMatnr("amazon");
                }else {
                    mat = matService.selectByMatnr("cloudWarehouse");
                }
            } else if (param.getFull() == 0) {
                //空托盘
                mat = matService.selectByMatnr("emptyPallet");
@@ -1285,6 +1331,7 @@
        WaitPakin waitPakin = new WaitPakin();
        waitPakin.sync(mat);
//        waitPakin.setMatnr(param.getMatnr());
        waitPakin.setBatch(String.valueOf(param.getBatchId()));
        waitPakin.setZpallet(param.getPalletId());   // 托盘码
@@ -1299,22 +1346,51 @@
        waitPakin.setOrigin(String.valueOf(param.getStorageArea()));//建议入库区域
        waitPakin.setManu(String.valueOf(param.getLocId()));//mes具体库位编号
        waitPakin.setThreeCode(param.getBizNo());
        // 7.3 新增字段复用现有组托档承载位,不新增列。
        waitPakin.setSuppCode(param.getCustomerId());
        waitPakin.setSupp(param.getCustomerName());
        waitPakin.setStandby3(param.getItem());
        waitPakin.setStandby1(param.getEntryWmsCode());
        waitPakin.setStandby2(param.getOutDoorNo());
        waitPakin.setBeBatch(param.getPackage1());//是否散货,0 非散货;1 散货;为了管控出货速率,散货可以出慢点。
        // ERP 入口默认打 erp,MQTT 组托会在参数里显式传 aws。
        waitPakin.setBoxType1(Cools.isEmpty(param.getBoxType1()) ? "erp" : param.getBoxType1());
        if (!waitPakinService.insert(waitPakin)) {
            throw new CoolException("保存入库通知档失败");
        }
        return null;
        return R.ok().add(Cools.add("palletId", param.getPalletId()).add("orderId", param.getOrderId()));
    }
    /**
     * 7.11 出库通知单(传递有序无序规则)单条建单。
     */
    @Override
    public R outOrder(OutTaskParam param) {
    public R outOrder(OutTaskParam param,int count) {
        LocMast locMast = locMastService.selectOne(new EntityWrapper<LocMast>().eq("loc_sts", "F").eq("barcode", param.getPalletId()));
        if (locMast == null) {
            return R.error("没有找到托盘码=" + param.getPalletId() + "对应的库位");
            throw new CoolException("没有找到托盘码=" + param.getPalletId() + "对应的库位");
        }
        if (Cools.isEmpty(param.getStationId())) {
            throw new CoolException("出库口编码不能为空");
        }
        if (Cools.isEmpty(param.getBatchSeq())) {
            throw new CoolException("批次标识不能为空");
        }
        if (param.getSeq() == null || param.getSeq() < 0) {
            throw new CoolException("出库顺序不能为空且不能小于0");
        }
        Integer ioType = 101;
        // 获取路径
        StaDesc staDesc = staDescService.queryCrnStn(ioType, locMast.getCrnNo(), Integer.valueOf(param.getStationId()));
        Integer stationId;
        try {
            stationId = Integer.valueOf(param.getStationId());
        } catch (NumberFormatException ex) {
            throw new CoolException("出库口编码格式错误:" + param.getStationId());
        }
        StaDesc staDesc = staDescService.queryCrnStn(ioType, locMast.getCrnNo(), stationId);
        if (staDesc == null) {
            throw new CoolException("未找到出库口=" + param.getStationId() + "对应路径");
        }
        Date now = new Date();
        // 生成工作号
        int workNo = commonService.getWorkNo(WorkNoType.getWorkNoType(ioType));
@@ -1335,7 +1411,10 @@
        wrkMast.setExitMk("N"); // 退出
        wrkMast.setEmptyMk("N"); // 空板
        wrkMast.setLinkMis("N");
        wrkMast.setPdcType("N");
        // 7.11:orderId 存 userNo,batchSeq 存批次标识,seq 存批次内顺序。
        wrkMast.setUserNo(param.getOrderId());//订单号
        wrkMast.setBatchSeq(param.getBatchSeq());//订单内批次标识
        wrkMast.setPltType(param.getSeq());//出库顺序,从1开始
        wrkMast.setTakeNone("0");  //0非自动
        wrkMast.setAppeUser(9995L); // 操作人员数据
@@ -1362,6 +1441,10 @@
            wrkDetl.setAppeUser(9995L);
            wrkDetl.setModiTime(now);
            wrkDetl.setModiUser(9995L);
            // 7.11:entryWmsCode、outDoorNo 复用明细备用字段。
            wrkDetl.setStandby1(param.getEntryWmsCode());
            wrkDetl.setStandby2(param.getOutDoorNo());
            wrkDetl.setSupp(param.getSeq()+"/"+count);
            if (!wrkDetlService.insert(wrkDetl)) {
                throw new CoolException("保存工作档明细失败");
@@ -1380,7 +1463,220 @@
            log.error(locMast.getLocNo() + "库位不是在库状态");
            throw new CoolException(locMast.getLocNo() + "库位不是在库状态");
        }
        return R.ok().add(Cools.add("wrkNo", workNo).add("orderId", param.getOrderId()));
    }
    /**
     * 7.11 出库通知单(传递有序无序规则)批量建单。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R outOrderBatch(List<OutTaskParam> params) {
        int n = params.size();
        Map<String, Integer> batchLineCounts = new HashMap<>();
        for (OutTaskParam outTaskParam : params) {
            batchLineCounts.merge(buildOutOrderBatchKey(outTaskParam), 1, Integer::sum);
        }
        for (OutTaskParam outTaskParam : params) {
            int count = batchLineCounts.getOrDefault(buildOutOrderBatchKey(outTaskParam), n);
            R r = outOrder(outTaskParam, count);
            if (!Objects.equals(r.get("code"), 200)) {
                throw new CoolException("出库建单失败");
            }
        }
        return R.ok();
    }
    /**
     * 7.9 出库异常变动上报。
     * WCS 只传 palletId、errorMsg,WMS 解析 orderId 后转发 ERP。
     */
    @Override
    public R outOrderAbnormalReport(OutOrderAbnormalReportParam param) {
        if (param == null || Cools.isEmpty(param.getPalletId())) {
            return R.error("palletId不能为空");
        }
        String orderId = resolveOutboundOrderId(param.getPalletId());
        if (Cools.isEmpty(orderId)) {
            return R.error("未找到托盘对应出库单号:" + param.getPalletId());
        }
        if (Cools.isEmpty(erpOutErrorAddress)) {
            return R.error("ERP出库异常上报地址未配置");
        }
        ErpOutOrderAbnormalReportParam erpParam = new ErpOutOrderAbnormalReportParam();
        erpParam.setPalletId(param.getPalletId());
        erpParam.setErrorMsg(param.getErrorMsg());
        erpParam.setOrderId(orderId);
        String requestJson = JSON.toJSONString(erpParam);
        String response = "";
        boolean pushOk = false;
        String errorMsg = null;
        String pushUrl = buildErpOutErrorRequestUrl();
        try {
            response = new HttpHandler.Builder()
                    .setUri(erpUrl)
                    .setPath(erpOutErrorAddress)
                    .setJson(requestJson)
                    .build()
                    .doPost();
            pushOk = isErpCallSuccess(response);
            if (!pushOk) {
                errorMsg = extractErpCallError(response);
            }
        } catch (Exception e) {
            errorMsg = e.getMessage();
            log.error("推ERP出库异常上报失败, palletId={}, orderId={}", param.getPalletId(), orderId, e);
        } finally {
            try {
                apiLogService.save(
                        "推ERP-出库异常上报",
                        pushUrl,
                        null,
                        "127.0.0.1",
                        requestJson,
                        response,
                        pushOk,
                        "palletId=" + param.getPalletId() + ",orderId=" + orderId
                );
            } catch (Exception logEx) {
                log.error("save out abnormal api log failed", logEx);
            }
        }
        if (!pushOk) {
            return R.error(Cools.isEmpty(errorMsg) ? "ERP出库异常上报失败" : errorMsg);
        }
        return R.ok().add(Cools.add("palletId", param.getPalletId()).add("orderId", orderId));
    }
    /**
     * 7.10 出库异常变动处理。
     * 本期只接收、校验和记录,不改本地任务,也不转发 WCS。
     */
    @Override
    public R outOrderAbnormalHandle(OutOrderAbnormalHandleParam param) {
        if (param == null || Cools.isEmpty(param.getPalletId())) {
            return R.error("palletId不能为空");
        }
        if (param.getOperateType() == null || !(Objects.equals(param.getOperateType(), 0) || Objects.equals(param.getOperateType(), 2))) {
            return R.error("operateType只允许0或2");
        }
        if (!existsOutboundPallet(param.getPalletId())) {
            return R.error("未找到托盘:" + param.getPalletId());
        }
        log.info("接收出库异常处理指令, palletId={}, operateType={}", param.getPalletId(), param.getOperateType());
        return R.ok().add(Cools.add("palletId", param.getPalletId()).add("operateType", param.getOperateType()));
    }
    private String buildOutOrderBatchKey(OutTaskParam param) {
        return param.getOrderId() + "#" + param.getBatchSeq();
    }
    private String resolveOutboundOrderId(String palletId) {
        List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                .eq("io_type", 101)
                .eq("barcode", palletId)
                .orderBy("io_time", false)
                .orderBy("wrk_no", false));
        if (!Cools.isEmpty(wrkMasts)) {
            for (WrkMast wrkMast : wrkMasts) {
                if (!Cools.isEmpty(wrkMast.getUserNo())) {
                    return wrkMast.getUserNo();
                }
            }
        }
        List<WrkDetl> wrkDetls = wrkDetlService.selectList(new EntityWrapper<WrkDetl>()
                .eq("zpallet", palletId)
                .orderBy("io_time", false)
                .orderBy("wrk_no", false));
        if (!Cools.isEmpty(wrkDetls)) {
            for (WrkDetl wrkDetl : wrkDetls) {
                if (!Cools.isEmpty(wrkDetl.getOrderNo())) {
                    return wrkDetl.getOrderNo();
                }
            }
        }
        return null;
    }
    private boolean existsOutboundPallet(String palletId) {
        if (locDetlService.selectCount(new EntityWrapper<LocDetl>().eq("zpallet", palletId)) > 0) {
            return true;
        }
        if (wrkMastService.selectCount(new EntityWrapper<WrkMast>().eq("io_type", 101).eq("barcode", palletId)) > 0) {
            return true;
        }
        return wrkDetlService.selectCount(new EntityWrapper<WrkDetl>().eq("zpallet", palletId)) > 0;
    }
    private boolean isErpCallSuccess(String response) {
        if (Cools.isEmpty(response)) {
            return false;
        }
        try {
            JSONObject jsonObject = JSON.parseObject(response);
            if (jsonObject == null) {
                return false;
            }
            Boolean success = jsonObject.getBoolean("success");
            if (success != null) {
                return success;
            }
            Integer code = jsonObject.getInteger("code");
            if (code != null) {
                return code == 200 || code == 0;
            }
            Integer status = jsonObject.getInteger("status");
            if (status != null) {
                return status == 200 || status == 0;
            }
            String result = jsonObject.getString("result");
            if (!Cools.isEmpty(result)) {
                return "success".equalsIgnoreCase(result) || "ok".equalsIgnoreCase(result) || "true".equalsIgnoreCase(result);
            }
        } catch (Exception ignore) {
            return response.toLowerCase().contains("success") && response.toLowerCase().contains("true");
        }
        return false;
    }
    private String extractErpCallError(String response) {
        if (Cools.isEmpty(response)) {
            return "empty erp response";
        }
        try {
            JSONObject jsonObject = JSON.parseObject(response);
            if (jsonObject == null) {
                return response;
            }
            String[] keys = new String[]{"msg", "message", "error", "errMsg"};
            for (String key : keys) {
                String value = jsonObject.getString(key);
                if (!Cools.isEmpty(value)) {
                    return value;
                }
            }
        } catch (Exception ignore) {
        }
        return response;
    }
    private String buildErpOutErrorRequestUrl() {
        if (Cools.isEmpty(erpUrl)) {
            return erpOutErrorAddress;
        }
        if (erpOutErrorAddress == null) {
            return erpUrl;
        }
        if (erpUrl.endsWith("/") && erpOutErrorAddress.startsWith("/")) {
            return erpUrl + erpOutErrorAddress.substring(1);
        }
        if (!erpUrl.endsWith("/") && !erpOutErrorAddress.startsWith("/")) {
            return erpUrl + "/" + erpOutErrorAddress;
        }
        return erpUrl + erpOutErrorAddress;
    }
}