| | |
| | | import com.zy.common.utils.NodeUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.PlatformTransactionManager; |
| | | import org.springframework.transaction.TransactionDefinition; |
| | |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.HashMap; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | /** |
| | | * Created by vincent on 2022/4/9 |
| | |
| | | private ApiLogService apiLogService; |
| | | @Autowired |
| | | private PlatformTransactionManager transactionManager; |
| | | |
| | | @Value("${mes-wk.base-url.test:http://192.168.0.41}") |
| | | private String mesWkBaseUrlTest; |
| | | |
| | | @Value("${mes-wk.base-url.prod:http://192.168.0.42}") |
| | | private String mesWkBaseUrlProd; |
| | | |
| | | @Value("${mes-wk.auth.ent-code:lfd}") |
| | | private String mesWkEntCode; |
| | | |
| | | @Value("${mes-wk.auth.username:TL002}") |
| | | private String mesWkUsername; |
| | | |
| | | @Value("${mes-wk.auth.password-md5:e10adc3949ba59abbe56e057f20f883e}") |
| | | private String mesWkPasswordMd5; |
| | | |
| | | @Value("${mes-wk.auth.token-valid-minutes:30}") |
| | | private Integer mesWkTokenValidMinutes; |
| | | |
| | | private final Map<String, MesWkTokenInfo> mesWkTokenCache = new ConcurrentHashMap<>(); |
| | | |
| | | @Override |
| | | @Transactional |
| | | public void pakinOrderCreate(OpenOrderPakinParam param) { |
| | |
| | | |
| | | List<ErpOrderDetl> detls = dto.getOrderItems(); |
| | | |
| | | // ===== 参数校验 ===== |
| | | if (Cools.isEmpty(order.getOrderNo()) || order.getOrderType() == null |
| | | || Cools.isEmpty(order.getWkType()) || order.getBusinessTime() == null |
| | | || order.getCreateTime() == null || Cools.isEmpty(order.getWarehouseId())) { |
| | | throw new CoolException("订单参数不完整"); |
| | | throw new CoolException("订单参数不完整:" + |
| | | "orderNo=" + order.getOrderNo() + |
| | | ",orderType=" + order.getOrderType() + |
| | | ",wkType=" + order.getWkType() + |
| | | ",warehouseId=" + order.getWarehouseId()); |
| | | } |
| | | |
| | | if (Cools.isEmpty(detls)) { |
| | | throw new CoolException("订单明细为空"); |
| | | throw new CoolException("订单明细为空:" + order.getOrderNo()); |
| | | } |
| | | |
| | | // ===== 调外部接口 ===== |
| | | for (ErpOrderDetl d : detls) { |
| | | if (d == null || Cools.isEmpty(d.getLineId()) |
| | | || Cools.isEmpty(d.getMatNr()) |
| | | || d.getAnfme() == null) { |
| | | String lineId = d != null ? d.getLineId() : null; |
| | | String matnr = d != null ? d.getMatNr() : null; |
| | | throw new CoolException("明细参数不完整:" + |
| | | "orderNo=" + order.getOrderNo() + |
| | | ",lineId=" + lineId + |
| | | ",matnr=" + matnr); |
| | | } |
| | | } |
| | | |
| | | if (!erpOrderService.insert(order)) { |
| | | throw new CoolException("订单插入失败:" + order.getOrderNo()); |
| | | } |
| | | |
| | | if (order.getId() == null) { |
| | | throw new CoolException("订单插入失败,未返回orderId:" + order.getOrderNo()); |
| | | } |
| | | |
| | | for (ErpOrderDetl detl : detls) { |
| | | detl.setOrderId(order.getId()); |
| | | if (!erpOrderDetlService.insert(detl)) { |
| | | throw new CoolException("订单明细插入失败:" + |
| | | "orderNo=" + order.getOrderNo() + |
| | | ",lineId=" + detl.getLineId()); |
| | | } |
| | | } |
| | | |
| | | String syncError = null; |
| | | String whId = order.getWarehouseId(); |
| | | Integer orderType = order.getOrderType(); |
| | |
| | | } else if ("WH3".equals(whId)) { |
| | | syncError = syncOrderToWarehouse3(order, detls, orderType); |
| | | } else { |
| | | throw new CoolException("未找到对应仓库编号"); |
| | | throw new CoolException("未找到对应仓库编号:" + |
| | | "orderNo=" + order.getOrderNo() + |
| | | ",warehouseId=" + whId); |
| | | } |
| | | } |
| | | |
| | | if (syncError != null) { |
| | | throw new CoolException(syncError); |
| | | } |
| | | |
| | | // ===== 插入主表 ===== |
| | | if (!erpOrderService.insert(order)) { |
| | | throw new CoolException("订单插入失败"); |
| | | } |
| | | |
| | | if (order.getId() == null) { |
| | | throw new CoolException("订单插入失败,未返回orderId"); |
| | | } |
| | | |
| | | // ===== 插入明细(这里异常会触发回滚)===== |
| | | for (ErpOrderDetl detl : detls) { |
| | | if (detl == null || Cools.isEmpty(detl.getLineId()) |
| | | || Cools.isEmpty(detl.getMatNr()) |
| | | || detl.getAnfme() == null) { |
| | | throw new CoolException("明细参数不完整"); |
| | | } |
| | | |
| | | detl.setOrderId(order.getId()); |
| | | |
| | | if (!erpOrderDetlService.insert(detl)) { |
| | | throw new CoolException("订单明细插入失败"); |
| | | } |
| | | throw new CoolException("外部接口同步失败:" + |
| | | "orderNo=" + order.getOrderNo() + |
| | | ",warehouseId=" + whId + |
| | | ",原因=" + syncError); |
| | | } |
| | | } |
| | | @Override |
| | |
| | | for (OrderDto dto : orders) { |
| | | String orderNo = dto != null ? dto.getOrderNo() : null; |
| | | try { |
| | | // ⭐ 调用独立事务方法 |
| | | processOneOrder(dto); |
| | | successOrders.add(orderNo); |
| | | } catch (Exception e) { |
| | | Map<String, Object> fail = new HashMap<>(); |
| | | String whId = dto != null ? dto.getWareHouseId() : null; |
| | | Integer orderType = dto != null ? dto.getOrderType() : null; |
| | | fail.put("orderNo", orderNo); |
| | | fail.put("warehouseId", whId); |
| | | fail.put("orderType", orderType); |
| | | fail.put("msg", e.getMessage()); |
| | | fail.put("exception", e.getClass().getSimpleName()); |
| | | Throwable cause = e.getCause(); |
| | | if (cause != null && cause != e) { |
| | | fail.put("cause", cause.getClass().getSimpleName() + ":" + cause.getMessage()); |
| | | } |
| | | failOrders.add(fail); |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> orderReport(OrderReportParam param) throws IOException { |
| | | if (param == null) { |
| | | throw new CoolException("参数不能为空"); |
| | | } |
| | | if (Cools.isEmpty(param.getWkType())) { |
| | | throw new CoolException("参数[wkType]不能为空"); |
| | | } |
| | | if (Cools.isEmpty(param.getOrderDetls())) { |
| | | throw new CoolException("参数[orderDetls]不能为空"); |
| | | } |
| | | |
| | | String baseUrl = resolveMesWkBaseUrl(param); |
| | | MesWkBizType bizType = resolveMesWkBizType(param.getWkType()); |
| | | |
| | | String token = getMesWkToken(baseUrl); |
| | | Map<String, Object> headers = new HashMap<>(); |
| | | headers.put("Authorization", token); |
| | | |
| | | String path; |
| | | String bizDesc; |
| | | Map<String, Object> payload; |
| | | |
| | | if (bizType == MesWkBizType.PURCHASE_IN_STOCK) { |
| | | path = "/purchaseInStock/lkPurchaseInStockAdd"; |
| | | bizDesc = "立库采购入库"; |
| | | payload = buildPurchaseInStockPayload(param.getOrderDetls()); |
| | | } else if (bizType == MesWkBizType.PRODUCT_ISSUE) { |
| | | path = "/productIssue/lkProductIssueAdd"; |
| | | bizDesc = "立库生产发料"; |
| | | payload = buildProductBizPayload(param.getOrderDetls(), "productIssueDtls", "productIssueBoms"); |
| | | } else if (bizType == MesWkBizType.PRODUCT_FEED) { |
| | | path = "/productFeed/lkProductFeedAdd"; |
| | | bizDesc = "立库生产补料"; |
| | | payload = buildProductBizPayload(param.getOrderDetls(), "productFeedDtls", "productFeedBoms"); |
| | | } else if (bizType == MesWkBizType.PRODUCT_RETURN) { |
| | | path = "/productReturn/lkProductReturnAdd"; |
| | | bizDesc = "立库生产退料"; |
| | | payload = buildProductBizPayload(param.getOrderDetls(), "productReturnDtls", "productReturnBoms"); |
| | | } else { |
| | | throw new CoolException("不支持的wkType:" + param.getWkType()); |
| | | } |
| | | |
| | | try { |
| | | Map<String, Object> res = callMesWkApi(baseUrl, path, headers, payload, bizDesc); |
| | | Map<String, Object> out = new HashMap<>(); |
| | | out.put("wkType", param.getWkType()); |
| | | out.put("baseUrl", baseUrl); |
| | | out.put("path", path); |
| | | out.put("result", res); |
| | | return out; |
| | | } catch (MesWkAuthException e) { |
| | | mesWkTokenCache.remove(baseUrl); |
| | | String newToken = getMesWkToken(baseUrl); |
| | | headers.put("Authorization", newToken); |
| | | Map<String, Object> res = callMesWkApi(baseUrl, path, headers, payload, bizDesc); |
| | | Map<String, Object> out = new HashMap<>(); |
| | | out.put("wkType", param.getWkType()); |
| | | out.put("baseUrl", baseUrl); |
| | | out.put("path", path); |
| | | out.put("result", res); |
| | | out.put("tokenRefreshed", true); |
| | | return out; |
| | | } |
| | | } |
| | | |
| | | private String getWarehouseBaseUrl(String wareHouseId) { |
| | |
| | | } |
| | | } |
| | | |
| | | private String resolveMesWkBaseUrl(OrderReportParam param) { |
| | | String raw = param != null ? param.getWarehouseId() : null; |
| | | if (!Cools.isEmpty(raw)) { |
| | | String v = raw.trim(); |
| | | if (v.startsWith("http://") || v.startsWith("https://")) { |
| | | return v; |
| | | } |
| | | if (v.matches("^\\d{1,3}(\\.\\d{1,3}){3}(:\\d+)?$")) { |
| | | return "http://" + v; |
| | | } |
| | | } |
| | | |
| | | String wkType = param != null ? param.getWkType() : null; |
| | | String norm = wkType == null ? "" : wkType.trim().toLowerCase(); |
| | | if (norm.contains("test") || norm.contains("41") || norm.contains("测试")) { |
| | | return normalizeHttpBaseUrl(mesWkBaseUrlTest); |
| | | } |
| | | return normalizeHttpBaseUrl(mesWkBaseUrlProd); |
| | | } |
| | | |
| | | private String normalizeHttpBaseUrl(String baseUrl) { |
| | | if (Cools.isEmpty(baseUrl)) { |
| | | return null; |
| | | } |
| | | String v = baseUrl.trim(); |
| | | if (v.startsWith("http://") || v.startsWith("https://")) { |
| | | return v; |
| | | } |
| | | return "http://" + v; |
| | | } |
| | | |
| | | private MesWkBizType resolveMesWkBizType(String wkType) { |
| | | String v = wkType == null ? "" : wkType.trim().toLowerCase(); |
| | | if (v.contains("purchase") || v.contains("purch") || v.contains("cg") || v.contains("采购") || v.equals("1")) { |
| | | return MesWkBizType.PURCHASE_IN_STOCK; |
| | | } |
| | | if (v.contains("issue") || v.contains("fl") || v.contains("发料") || v.equals("2")) { |
| | | return MesWkBizType.PRODUCT_ISSUE; |
| | | } |
| | | if (v.contains("feed") || v.contains("bl") || v.contains("补料") || v.equals("3")) { |
| | | return MesWkBizType.PRODUCT_FEED; |
| | | } |
| | | if (v.contains("return") || v.contains("tl") || v.contains("退料") || v.equals("4")) { |
| | | return MesWkBizType.PRODUCT_RETURN; |
| | | } |
| | | throw new CoolException("无法识别wkType:" + wkType); |
| | | } |
| | | |
| | | private synchronized String getMesWkToken(String baseUrl) throws IOException { |
| | | if (Cools.isEmpty(baseUrl)) { |
| | | throw new CoolException("服务器地址未配置"); |
| | | } |
| | | long now = System.currentTimeMillis(); |
| | | MesWkTokenInfo cached = mesWkTokenCache.get(baseUrl); |
| | | if (cached != null && !Cools.isEmpty(cached.token) && now < cached.expireAt) { |
| | | return cached.token; |
| | | } |
| | | |
| | | String path = "/acco/login"; |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | payload.put("entCode", mesWkEntCode); |
| | | payload.put("username", mesWkUsername); |
| | | payload.put("password", mesWkPasswordMd5); |
| | | String requestJson = JSON.toJSONString(payload); |
| | | String url = baseUrl + path; |
| | | |
| | | String response = new HttpHandler.Builder() |
| | | .setUri(baseUrl) |
| | | .setPath(path) |
| | | .setJson(requestJson) |
| | | .build() |
| | | .doPost(); |
| | | |
| | | if (Cools.isEmpty(response)) { |
| | | callApiDetlLogSave("立库接口登录", url, requestJson, "接口无响应", false); |
| | | throw new CoolException("立库接口登录无响应"); |
| | | } |
| | | |
| | | JSONObject json = JSON.parseObject(response); |
| | | if (json == null) { |
| | | callApiDetlLogSave("立库接口登录", url, requestJson, response, false); |
| | | throw new CoolException("立库接口登录响应格式错误"); |
| | | } |
| | | |
| | | Integer code = json.getInteger("code"); |
| | | if (code == null || (code != 200 && code != 0)) { |
| | | callApiDetlLogSave("立库接口登录", url, requestJson, response, false); |
| | | throw new CoolException("立库接口登录失败:" + response); |
| | | } |
| | | |
| | | String token = json.getString("data"); |
| | | if (Cools.isEmpty(token)) { |
| | | callApiDetlLogSave("立库接口登录", url, requestJson, response, false); |
| | | throw new CoolException("立库接口登录未返回token"); |
| | | } |
| | | |
| | | callApiDetlLogSave("立库接口登录", url, requestJson, response, true); |
| | | long expireAt = now + (mesWkTokenValidMinutes == null ? 30 : mesWkTokenValidMinutes) * 60L * 1000L; |
| | | mesWkTokenCache.put(baseUrl, new MesWkTokenInfo(token, expireAt)); |
| | | return token; |
| | | } |
| | | |
| | | private Map<String, Object> callMesWkApi(String baseUrl, String path, Map<String, Object> headers, Map<String, Object> payload, String bizDesc) throws IOException { |
| | | String url = baseUrl + path; |
| | | String requestJson = JSON.toJSONString(payload == null ? new HashMap<>() : payload); |
| | | String response; |
| | | try { |
| | | response = new HttpHandler.Builder() |
| | | .setUri(baseUrl) |
| | | .setPath(path) |
| | | .setHeaders(headers) |
| | | .setJson(requestJson) |
| | | .build() |
| | | .doPost(); |
| | | } catch (Exception e) { |
| | | callApiDetlLogSave(bizDesc, url, requestJson, e.getMessage(), false); |
| | | throw e; |
| | | } |
| | | |
| | | if (Cools.isEmpty(response)) { |
| | | callApiDetlLogSave(bizDesc, url, requestJson, "接口无响应", false); |
| | | throw new CoolException("接口无响应"); |
| | | } |
| | | |
| | | JSONObject json = JSON.parseObject(response); |
| | | if (json == null) { |
| | | callApiDetlLogSave(bizDesc, url, requestJson, response, false); |
| | | throw new CoolException("接口响应格式错误"); |
| | | } |
| | | |
| | | Integer code = json.getInteger("code"); |
| | | if (code != null && (code == 200 || code == 0)) { |
| | | callApiDetlLogSave(bizDesc, url, requestJson, response, true); |
| | | return JSON.parseObject(response, Map.class); |
| | | } |
| | | |
| | | String err = json.getString("error"); |
| | | String msg = json.getString("msg"); |
| | | String text = !Cools.isEmpty(err) ? err : (!Cools.isEmpty(msg) ? msg : "未知错误"); |
| | | callApiDetlLogSave(bizDesc, url, requestJson, response, false); |
| | | if (text.toLowerCase().contains("token") || text.toLowerCase().contains("unauthor")) { |
| | | throw new MesWkAuthException(text); |
| | | } |
| | | throw new CoolException(text); |
| | | } |
| | | |
| | | private Map<String, Object> buildPurchaseInStockPayload(List<OrderDetl> orderDetls) { |
| | | List<Map<String, Object>> dtls = new ArrayList<>(); |
| | | for (OrderDetl d : orderDetls) { |
| | | if (d == null) { |
| | | continue; |
| | | } |
| | | if (Cools.isEmpty(d.getBatch())) { |
| | | throw new CoolException("orderDetls.batch不能为空"); |
| | | } |
| | | if (d.getAnfme() == null) { |
| | | throw new CoolException("orderDetls.anfme不能为空"); |
| | | } |
| | | Map<String, Object> one = new HashMap<>(); |
| | | one.put("batchNum", d.getBatch()); |
| | | one.put("quantity", d.getAnfme()); |
| | | dtls.add(one); |
| | | } |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | payload.put("purchaseInStockDtls", dtls); |
| | | return payload; |
| | | } |
| | | |
| | | private Map<String, Object> buildProductBizPayload(List<OrderDetl> orderDetls, String dtlsKey, String bomsKey) { |
| | | Map<String, List<OrderDetl>> bySku = new LinkedHashMap<>(); |
| | | for (OrderDetl d : orderDetls) { |
| | | if (d == null) { |
| | | continue; |
| | | } |
| | | if (Cools.isEmpty(d.getSku())) { |
| | | throw new CoolException("orderDetls.sku不能为空"); |
| | | } |
| | | bySku.computeIfAbsent(d.getSku(), k -> new ArrayList<>()).add(d); |
| | | } |
| | | |
| | | List<Map<String, Object>> dtls = new ArrayList<>(); |
| | | for (Map.Entry<String, List<OrderDetl>> entry : bySku.entrySet()) { |
| | | String sku = entry.getKey(); |
| | | List<OrderDetl> list = entry.getValue(); |
| | | |
| | | List<Map<String, Object>> boms = new ArrayList<>(); |
| | | for (OrderDetl d : list) { |
| | | if (Cools.isEmpty(d.getBatch())) { |
| | | throw new CoolException("orderDetls.batch不能为空"); |
| | | } |
| | | if (d.getAnfme() == null) { |
| | | throw new CoolException("orderDetls.anfme不能为空"); |
| | | } |
| | | Map<String, Object> bom = new HashMap<>(); |
| | | bom.put("batchNum", d.getBatch()); |
| | | bom.put("quantity", d.getAnfme()); |
| | | boms.add(bom); |
| | | } |
| | | |
| | | Map<String, Object> one = new HashMap<>(); |
| | | one.put("planSpNo", sku); |
| | | one.put(bomsKey, boms); |
| | | dtls.add(one); |
| | | } |
| | | |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | payload.put(dtlsKey, dtls); |
| | | return payload; |
| | | } |
| | | |
| | | private enum MesWkBizType { |
| | | PURCHASE_IN_STOCK, |
| | | PRODUCT_ISSUE, |
| | | PRODUCT_FEED, |
| | | PRODUCT_RETURN |
| | | } |
| | | |
| | | private static class MesWkTokenInfo { |
| | | private final String token; |
| | | private final long expireAt; |
| | | |
| | | private MesWkTokenInfo(String token, long expireAt) { |
| | | this.token = token; |
| | | this.expireAt = expireAt; |
| | | } |
| | | } |
| | | |
| | | private static class MesWkAuthException extends RuntimeException { |
| | | private MesWkAuthException(String message) { |
| | | super(message); |
| | | } |
| | | } |
| | | |
| | | // 模拟调用仓库1接口 |
| | | private String syncOrderToWarehouse1(ErpOrder order, List<ErpOrderDetl> details, Integer orderType) { |
| | | log.info("Calling Warehouse 1 API for order: {}", order.getOrderNo()); |