| | |
| | | package com.vincent.rsf.server.api.service.impl; |
| | | |
| | | import com.vincent.rsf.server.api.config.RemotesInfoProperties; |
| | | import com.vincent.rsf.server.api.controller.erp.params.DapIlcwmsCompletionLine; |
| | | import com.vincent.rsf.server.api.controller.erp.params.DapIlcwmsCompletionRequest; |
| | | import com.vincent.rsf.server.api.controller.erp.params.InOutResultReportParam; |
| | | import com.vincent.rsf.server.api.controller.erp.params.InventoryAdjustReportParam; |
| | | 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; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * 立库侧请求云仓:入/出库结果上报(9.1)、库存调整主动上报(9.2)、物料基础信息同步。 |
| | | * 使用 OpenFeign 调用;可选 HttpEntity(RestTemplate) 方式已注释保留。 |
| | | * 立库侧请求云仓:ICusStockService 入库/出库/调拨完成反馈;9.2 增减调整仍为 /api/report/inventoryAdjust,报文同为 {data:[]}。 |
| | | */ |
| | | @Slf4j |
| | | @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 RemotesInfoProperties.ApiInfo erpApiInfo; |
| | | |
| | | @Autowired |
| | | private CloudWmsErpFeignClient cloudWmsErpFeignClient; |
| | | |
| | | /** |
| | | * 可选:改用 HttpEntity(RestTemplate) 调用云仓时启用。 |
| | | */ |
| | | // @Autowired |
| | | // private RestTemplate restTemplate; |
| | | @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<>()); |
| | | } |
| | |
| | | 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("云仓地址未配置,未实际上报"); |
| | | } |
| | | return cloudWmsErpFeignClient.reportInOutResult(param); |
| | | 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) { |
| | | 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); |
| | | return DapIlcwmsResponseNormalizer.toNotifyFormat(raw); |
| | | } |
| | | |
| | | @Override |
| | |
| | | return resultMap(400, "参数不能为空", null); |
| | | } |
| | | if (!isCloudWmsConfigured()) { |
| | | log.warn("ErpApi(云仓WMS) 未配置 host,跳过 9.2 库存调整上报,物料:{}", param.getMatNr()); |
| | | return stubSuccess("云仓地址未配置,未实际上报"); |
| | | log.warn("ErpApi(云仓WMS) 未配置 host/base-url,跳过 9.2 库存调整上报,物料:{}", param.getMatNr()); |
| | | return stubWithoutUpstream("云仓地址未配置,未实际上报"); |
| | | } |
| | | return cloudWmsErpFeignClient.reportInventoryAdjust(param); |
| | | Integer changeType = param.getChangeType(); |
| | | if (changeType == null) { |
| | | return resultMap(400, "changeType 不能为空", null); |
| | | } |
| | | DapIlcwmsCompletionRequest req = new DapIlcwmsCompletionRequest(); |
| | | if (changeType == 3) { |
| | | String baseSeq = StringUtils.isNotBlank(param.getDocSeqNo()) ? param.getDocSeqNo() : "1"; |
| | | List<DapIlcwmsCompletionLine> lines = new ArrayList<>(2); |
| | | lines.add(buildAdjustLine(param, false, true, baseSeq + "-O")); |
| | | lines.add(buildAdjustLine(param, true, false, baseSeq + "-I")); |
| | | req.setData(lines); |
| | | } else if (changeType == 1) { |
| | | req.setData(Collections.singletonList(buildAdjustLine(param, true, false, null))); |
| | | } else if (changeType == 2) { |
| | | req.setData(Collections.singletonList(buildAdjustLine(param, false, true, null))); |
| | | } 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); |
| | | return changeType == 3 |
| | | ? DapIlcwmsResponseNormalizer.toNotifyFormat(raw) |
| | | : DapIlcwmsResponseNormalizer.toNotifyFormatFlexible(raw); |
| | | } |
| | | |
| | | private DapIlcwmsCompletionLine buildInOutLine(InOutResultReportParam param, boolean inbound) { |
| | | CloudMatnrParts matnrParts = parseCloudMatnr(param.getMatNr()); |
| | | String docType = StringUtils.isNotBlank(param.getWkType()) |
| | | ? param.getWkType() |
| | | : (inbound ? "IN" : "OUT"); |
| | | String unitNo = StringUtils.isNotBlank(param.getUnitNo()) ? param.getUnitNo().trim() : "PCS"; |
| | | 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(orgNoLine) |
| | | .setDocWarehouseNo(docWh) |
| | | .setDocType(docType) |
| | | .setDocNo(param.getOrderNo()) |
| | | .setDocSeqNo(StringUtils.isNotBlank(param.getLineId()) ? param.getLineId() : "1") |
| | | // 按云仓规则拆分物料编码 |
| | | .setItemNo(matnrParts.getItemNo()) |
| | | .setQty(parseQty(param.getQty())) |
| | | .setUnitNo(unitNo) |
| | | .setCombinationLotNo(matnrParts.getCombinationLotNo()) |
| | | .setBarcode(matnrParts.getBarcode()); |
| | | if (inbound) { |
| | | line.setInWarehouseNo(inWhLine).setInCellNo(inWhLine); |
| | | // line.setInWarehouseNo(inWhLine).setInCellNo(param.getLocId()); |
| | | } else { |
| | | line.setOutWarehouseNo(outWhLine).setOutCellNo(outWhLine); |
| | | // line.setOutWarehouseNo(outWhLine).setOutCellNo(param.getLocId()); |
| | | } |
| | | return line; |
| | | } |
| | | |
| | | /** |
| | | * @param fillIn 是否填入库储位 |
| | | * @param fillOut 是否填出库储位 |
| | | * @param docSeqOverride 非空时用作项次(移库第二行等) |
| | | */ |
| | | private DapIlcwmsCompletionLine buildAdjustLine(InventoryAdjustReportParam param, boolean fillIn, boolean fillOut, String docSeqOverride) { |
| | | 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() : "PCS"; |
| | | CloudMatnrParts matnrParts = parseCloudMatnr(param.getMatNr()); |
| | | DapIlcwmsCompletionLine line = new DapIlcwmsCompletionLine() |
| | | .setOrgNo(DEFAULT_CLOUD_ORG_NO) |
| | | .setDocType(docType) |
| | | .setDocNo(docNo) |
| | | .setDocSeqNo(docSeq) |
| | | // 按云仓规则拆分物料编码 |
| | | .setItemNo(matnrParts.getItemNo()) |
| | | .setQty(parseQty(param.getQty())) |
| | | .setUnitNo(unit) |
| | | .setCombinationLotNo(matnrParts.getCombinationLotNo()) |
| | | .setBarcode(matnrParts.getBarcode()); |
| | | if (fillIn) { |
| | | line.setInWarehouseNo(param.getWareHouseId()); |
| | | line.setInCellNo(StringUtils.isNotBlank(param.getTargetLocId()) ? param.getTargetLocId() : param.getSourceLocId()); |
| | | } |
| | | if (fillOut) { |
| | | line.setOutWarehouseNo(param.getWareHouseId()); |
| | | line.setOutCellNo(StringUtils.isNotBlank(param.getSourceLocId()) ? param.getSourceLocId() : param.getTargetLocId()); |
| | | } |
| | | return line; |
| | | } |
| | | |
| | | private static String resolveAdjustDocType(InventoryAdjustReportParam param) { |
| | | if (StringUtils.isNotBlank(param.getDocType())) { |
| | | return param.getDocType(); |
| | | } |
| | | Integer ct = param.getChangeType(); |
| | | if (ct != null && ct == 2) { |
| | | return "OUT"; |
| | | } |
| | | if (ct != null && ct == 3) { |
| | | return "ADJ"; |
| | | } |
| | | return "IN"; |
| | | } |
| | | |
| | | // 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); |
| | | } |
| | | String[] arr = matNr.split("#", -1); |
| | | if (arr.length >= 3) { |
| | | return new CloudMatnrParts(arr[0], arr[1], matNr); |
| | | } |
| | | return new CloudMatnrParts(matNr, null, matNr); |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | |
| | | public String getItemNo() { |
| | | return itemNo; |
| | | } |
| | | |
| | | public String getCombinationLotNo() { |
| | | return combinationLotNo; |
| | | } |
| | | |
| | | public String getBarcode() { |
| | | return barcode; |
| | | } |
| | | } |
| | | |
| | | private static Double parseQty(String q) { |
| | | if (StringUtils.isBlank(q)) { |
| | | return 0d; |
| | | } |
| | | try { |
| | | return Double.parseDouble(q.trim()); |
| | | } catch (NumberFormatException e) { |
| | | return 0d; |
| | | } |
| | | } |
| | | |
| | | 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) { |
| | |
| | | return map; |
| | | } |
| | | |
| | | // ========== 可选:HttpEntity(RestTemplate) 方式(当前未使用) ========== |
| | | // 启用步骤:1)取消上方 restTemplate 的 @Autowired 注入; |
| | | // 2)取消下面整段注释,恢复 buildUrl、postToCloudWms、parseResponse 方法及 OBJECT_MAPPER; |
| | | // 3)在 syncMatnrsToCloud/reportInOutResult/reportInventoryAdjust 中改为:String url = buildUrl(erpApiInfo.getXxxPath()); if (url == null) return stubSuccess(...); return postToCloudWms(url, body); |
| | | // |
| | | // private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); |
| | | // private String buildUrl(String path) { ... } |
| | | // private Map<String, Object> postToCloudWms(String url, Object body) { HttpHeaders headers = ...; HttpEntity<Object> entity = new HttpEntity<>(body, headers); ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); return parseResponse(response.getBody()); } |
| | | // private Map<String, Object> parseResponse(String json) { ... } |
| | | 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()); |
| | | } |
| | | } |
| | | } |