cl
6 天以前 cb2f02d60aac235f2f9e5ef777e0141fb697c264
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/CloudWmsReportServiceImpl.java
@@ -8,6 +8,7 @@
import com.vincent.rsf.server.api.feign.CloudWmsErpFeignClient;
import com.vincent.rsf.server.api.integration.dap.DapIlcwmsResponseNormalizer;
import com.vincent.rsf.server.api.service.CloudWmsReportService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -26,17 +27,24 @@
@Service
public class CloudWmsReportServiceImpl implements CloudWmsReportService {
    /** 云仓回馈:通知单 orgNo 为空时的默认组织 */
    private static final String DEFAULT_CLOUD_ORG_NO = "1";
    /** 云仓回馈:通知单 inWarehouseNo / outWarehouseNo 为空时的默认仓编码 */
    private static final String DEFAULT_CLOUD_WH_NO = "101";
    @Autowired
    private RemotesInfoProperties erpApi;
    @Autowired
    private CloudWmsErpFeignClient cloudWmsErpFeignClient;
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public Map<String, Object> syncMatnrsToCloud(Object body) {
        if (!isCloudWmsConfigured()) {
            log.warn("ErpApi(云仓WMS) 未配置 host,跳过物料基础信息同步");
            return stubSuccess("云仓地址未配置,未实际同步");
            log.warn("ErpApi(云仓WMS) 未配置 host/base-url,跳过物料基础信息同步");
            return stubWithoutUpstream("云仓地址未配置,未实际同步");
        }
        return cloudWmsErpFeignClient.syncMatnrs(body != null ? body : new HashMap<>());
    }
@@ -47,16 +55,50 @@
            return resultMap(400, "参数不能为空", null);
        }
        if (!isCloudWmsConfigured()) {
            log.warn("ErpApi(云仓WMS) 未配置 host,跳过 9.1 入/出库结果上报,订单:{}", param.getOrderNo());
            return stubSuccess("云仓地址未配置,未实际上报");
            log.warn("ErpApi(云仓WMS) 未配置 host/base-url,跳过 9.1 入/出库结果上报,订单:{}", param.getOrderNo());
            return stubWithoutUpstream("云仓地址未配置,未实际上报");
        }
        String err = validateDapBase();
        String err = validateDapBaseForInOut(param);
        if (err != null) {
            return resultMap(400, err, null);
        }
        boolean inbound = param.getInbound() == null || Boolean.TRUE.equals(param.getInbound());
        DapIlcwmsCompletionRequest req = new DapIlcwmsCompletionRequest()
                .setData(Collections.singletonList(buildInOutLine(param, inbound)));
        logOutboundPayload("IN_OUT_RESULT", inbound ? "stockInCompleted" : "stockOutCompleted", req);
        Map<String, Object> raw = inbound
                ? cloudWmsErpFeignClient.cusInventoryCompletionReport(req)
                : cloudWmsErpFeignClient.cusOutboundCompletionReport(req);
        return DapIlcwmsResponseNormalizer.toNotifyFormat(raw);
    }
    @Override
    public Map<String, Object> reportInOutResults(List<InOutResultReportParam> lines) {
        if (lines == null || lines.isEmpty()) {
            return resultMap(400, "明细不能为空", null);
        }
        if (!isCloudWmsConfigured()) {
            log.warn("ErpApi(云仓WMS) 未配置 host/base-url,跳过 9.1 入出库合并上报");
            return stubWithoutUpstream("云仓地址未配置,未实际上报");
        }
        InOutResultReportParam first = lines.get(0);
        boolean inbound = first.getInbound() == null || Boolean.TRUE.equals(first.getInbound());
        for (InOutResultReportParam param : lines) {
            String err = validateDapBaseForInOut(param);
            if (err != null) {
                return resultMap(400, err, null);
            }
            boolean rowIn = param.getInbound() == null || Boolean.TRUE.equals(param.getInbound());
            if (rowIn != inbound) {
                return resultMap(400, "合并上报须同为入库或同为出库", null);
            }
        }
        List<DapIlcwmsCompletionLine> data = new ArrayList<>(lines.size());
        for (InOutResultReportParam param : lines) {
            data.add(buildInOutLine(param, inbound));
        }
        DapIlcwmsCompletionRequest req = new DapIlcwmsCompletionRequest().setData(data);
        logOutboundPayload("IN_OUT_RESULT_BATCH", inbound ? "stockInCompleted" : "stockOutCompleted", req);
        Map<String, Object> raw = inbound
                ? cloudWmsErpFeignClient.cusInventoryCompletionReport(req)
                : cloudWmsErpFeignClient.cusOutboundCompletionReport(req);
@@ -69,12 +111,8 @@
            return resultMap(400, "参数不能为空", null);
        }
        if (!isCloudWmsConfigured()) {
            log.warn("ErpApi(云仓WMS) 未配置 host,跳过 9.2 库存调整上报,物料:{}", param.getMatNr());
            return stubSuccess("云仓地址未配置,未实际上报");
        }
        String err = validateDapBase();
        if (err != null) {
            return resultMap(400, err, null);
            log.warn("ErpApi(云仓WMS) 未配置 host/base-url,跳过 9.2 库存调整上报,物料:{}", param.getMatNr());
            return stubWithoutUpstream("云仓地址未配置,未实际上报");
        }
        Integer changeType = param.getChangeType();
        if (changeType == null) {
@@ -94,6 +132,7 @@
        } else {
            return resultMap(400, "不支持的 changeType:" + changeType, null);
        }
        logOutboundPayload("INVENTORY_ADJUST", changeType == 3 ? "stockTransferCompleted" : "reportInventoryAdjust", req);
        Map<String, Object> raw = changeType == 3
                ? cloudWmsErpFeignClient.stockTransferCompleted(req)
                : cloudWmsErpFeignClient.reportInventoryAdjust(req);
@@ -102,22 +141,39 @@
                : DapIlcwmsResponseNormalizer.toNotifyFormatFlexible(raw);
    }
    private String validateDapBaseForInOut(InOutResultReportParam param) {
        if (param != null && StringUtils.isBlank(param.getUnitNo())) {
            return "unitNo 不能为空";
        }
        return null;
    }
    private DapIlcwmsCompletionLine buildInOutLine(InOutResultReportParam param, boolean inbound) {
        RemotesInfoProperties.Dap dap = erpApi.getDap();
        CloudMatnrParts matnrParts = parseCloudMatnr(param.getMatNr());
        String docType = StringUtils.isNotBlank(param.getWkType())
                ? param.getWkType()
                : (inbound ? "IN" : "OUT");
        String unitNo = StringUtils.trimToEmpty(param.getUnitNo());
        String orgNoLine = StringUtils.isNotBlank(param.getOrgNo()) ? param.getOrgNo().trim() : DEFAULT_CLOUD_ORG_NO;
        String docWh = StringUtils.trimToNull(param.getDocWarehouseNo());
        String inWhLine = StringUtils.isNotBlank(param.getInWarehouseNo()) ? param.getInWarehouseNo().trim() : DEFAULT_CLOUD_WH_NO;
        String outWhLine = StringUtils.isNotBlank(param.getOutWarehouseNo()) ? param.getOutWarehouseNo().trim() : DEFAULT_CLOUD_WH_NO;
        DapIlcwmsCompletionLine line = new DapIlcwmsCompletionLine()
                .setOrgNo(dap.getOrgNo())
                .setDocType(inbound ? dap.getDocTypeIn() : dap.getDocTypeOut())
                .setOrgNo(orgNoLine)
                .setDocWarehouseNo(docWh)
                .setDocType(docType)
                .setDocNo(param.getOrderNo())
                .setDocSeqNo(StringUtils.isNotBlank(param.getLineId()) ? param.getLineId() : "1")
                .setItemNo(param.getMatNr())
                // 按云仓规则拆分物料编码
                .setItemNo(matnrParts.getItemNo())
                .setQty(parseQty(param.getQty()))
                .setUnitNo(dap.getUnitNo())
                .setCombinationLotNo(param.getBatch())
                .setBarcode(resolveBarcode(param));
                .setUnitNo(unitNo)
                .setCombinationLotNo(matnrParts.getCombinationLotNo())
                .setBarcode(matnrParts.getBarcode());
        if (inbound) {
            line.setInWarehouseNo(param.getWareHouseId()).setInCellNo(param.getLocId());
            line.setInWarehouseNo(inWhLine).setInCellNo(param.getLocId());
        } else {
            line.setOutWarehouseNo(param.getWareHouseId()).setOutCellNo(param.getLocId());
            line.setOutWarehouseNo(outWhLine).setOutCellNo(param.getLocId());
        }
        return line;
    }
@@ -128,22 +184,23 @@
     * @param docSeqOverride 非空时用作项次(移库第二行等)
     */
    private DapIlcwmsCompletionLine buildAdjustLine(InventoryAdjustReportParam param, boolean fillIn, boolean fillOut, String docSeqOverride) {
        RemotesInfoProperties.Dap dap = erpApi.getDap();
        String docType = resolveAdjustDocType(param, dap);
        String docType = resolveAdjustDocType(param);
        String docNo = StringUtils.isNotBlank(param.getDocNo()) ? param.getDocNo() : "ADJ";
        String docSeq = docSeqOverride != null ? docSeqOverride
                : (StringUtils.isNotBlank(param.getDocSeqNo()) ? param.getDocSeqNo() : "1");
        String unit = StringUtils.isNotBlank(param.getUnitNo()) ? param.getUnitNo() : dap.getUnitNo();
        String unit = StringUtils.isNotBlank(param.getUnitNo()) ? param.getUnitNo() : "PCS";
        CloudMatnrParts matnrParts = parseCloudMatnr(param.getMatNr());
        DapIlcwmsCompletionLine line = new DapIlcwmsCompletionLine()
                .setOrgNo(dap.getOrgNo())
                .setOrgNo(DEFAULT_CLOUD_ORG_NO)
                .setDocType(docType)
                .setDocNo(docNo)
                .setDocSeqNo(docSeq)
                .setItemNo(param.getMatNr())
                // 按云仓规则拆分物料编码
                .setItemNo(matnrParts.getItemNo())
                .setQty(parseQty(param.getQty()))
                .setUnitNo(unit)
                .setCombinationLotNo(param.getBatch())
                .setBarcode(resolveAdjustBarcode(param));
                .setCombinationLotNo(matnrParts.getCombinationLotNo())
                .setBarcode(matnrParts.getBarcode());
        if (fillIn) {
            line.setInWarehouseNo(param.getWareHouseId());
            line.setInCellNo(StringUtils.isNotBlank(param.getTargetLocId()) ? param.getTargetLocId() : param.getSourceLocId());
@@ -155,41 +212,79 @@
        return line;
    }
    private static String resolveAdjustDocType(InventoryAdjustReportParam param, RemotesInfoProperties.Dap dap) {
    private static String resolveAdjustDocType(InventoryAdjustReportParam param) {
        if (StringUtils.isNotBlank(param.getDocType())) {
            return param.getDocType();
        }
        Integer ct = param.getChangeType();
        if (ct != null && ct == 2) {
            return dap.getDocTypeOut();
            return "OUT";
        }
        if (ct != null && ct == 3) {
            return dap.getDocTypeAdj();
            return "ADJ";
        }
        return dap.getDocTypeIn();
        return "IN";
    }
    private static String resolveBarcode(InOutResultReportParam param) {
        if (StringUtils.isNotBlank(param.getBarcode())) {
            return param.getBarcode();
    // private static String resolveBarcode(InOutResultReportParam param) {
    //     if (StringUtils.isNotBlank(param.getBarcode())) {
    //         return param.getBarcode();
    //     }
    //     if (StringUtils.isNotBlank(param.getPalletId())) {
    //         return param.getPalletId();
    //     }
    //     if (param.getMatNr() != null && param.getLocId() != null) {
    //         return param.getMatNr() + ":" + param.getLocId();
    //     }
    //     return param.getMatNr();
    // }
    //
    // private static String resolveAdjustBarcode(InventoryAdjustReportParam param) {
    //     if (StringUtils.isNotBlank(param.getBarcode())) {
    //         return param.getBarcode();
    //     }
    //     if (StringUtils.isNotBlank(param.getPalletId())) {
    //         return param.getPalletId();
    //     }
    //     return param.getMatNr();
    // }
    /**
     * 云仓回报字段映射:三段式取前两段,barcode 保留原串。
     */
    private static CloudMatnrParts parseCloudMatnr(String matNr) {
        if (StringUtils.isBlank(matNr)) {
            return new CloudMatnrParts(null, null, null);
        }
        if (StringUtils.isNotBlank(param.getPalletId())) {
            return param.getPalletId();
        String[] arr = matNr.split("#", -1);
        if (arr.length >= 3) {
            return new CloudMatnrParts(arr[0], arr[1], matNr);
        }
        if (param.getMatNr() != null && param.getLocId() != null) {
            return param.getMatNr() + ":" + param.getLocId();
        }
        return param.getMatNr();
        return new CloudMatnrParts(matNr, null, matNr);
    }
    private static String resolveAdjustBarcode(InventoryAdjustReportParam param) {
        if (StringUtils.isNotBlank(param.getBarcode())) {
            return param.getBarcode();
    private static class CloudMatnrParts {
        private final String itemNo;
        private final String combinationLotNo;
        private final String barcode;
        private CloudMatnrParts(String itemNo, String combinationLotNo, String barcode) {
            this.itemNo = itemNo;
            this.combinationLotNo = combinationLotNo;
            this.barcode = barcode;
        }
        if (StringUtils.isNotBlank(param.getPalletId())) {
            return param.getPalletId();
        public String getItemNo() {
            return itemNo;
        }
        return param.getMatNr();
        public String getCombinationLotNo() {
            return combinationLotNo;
        }
        public String getBarcode() {
            return barcode;
        }
    }
    private static Double parseQty(String q) {
@@ -203,23 +298,20 @@
        }
    }
    private String validateDapBase() {
        RemotesInfoProperties.Dap d = erpApi.getDap();
        if (d == null || StringUtils.isBlank(d.getOrgNo())) {
            return "未配置 platform.erp.dap.org-no";
        }
        return null;
    }
    private boolean isCloudWmsConfigured() {
        String host = erpApi.getHost();
        return host != null && !host.trim().isEmpty();
        if (host != null && !host.trim().isEmpty()) {
            return true;
        }
        String baseUrl = erpApi.getBaseUrl();
        return baseUrl != null && !baseUrl.trim().isEmpty();
    }
    private Map<String, Object> stubSuccess(String msg) {
    /** 未走云仓 HTTP,code 非 200,避免调度误判成功 */
    private Map<String, Object> stubWithoutUpstream(String msg) {
        Map<String, Object> data = new HashMap<>();
        data.put("result", "SUCCESS");
        return resultMap(200, msg, data);
        data.put("result", "SKIPPED");
        return resultMap(503, msg, data);
    }
    private Map<String, Object> resultMap(int code, String msg, Map<String, Object> data) {
@@ -229,4 +321,14 @@
        map.put("data", data);
        return map;
    }
    private void logOutboundPayload(String reportType, String endpoint, DapIlcwmsCompletionRequest req) {
        try {
            log.info("云仓真实请求报文,reportType={},endpoint={},payload={}",
                    reportType, endpoint, objectMapper.writeValueAsString(req));
        } catch (Exception e) {
            log.warn("云仓真实请求报文序列化失败,reportType={},endpoint={}:{}",
                    reportType, endpoint, e.getMessage());
        }
    }
}