|
@@ -1,8 +1,17 @@
|
|
|
package com.malk.service.wangdian;
|
|
package com.malk.service.wangdian;
|
|
|
|
|
|
|
|
import com.alibaba.fastjson.JSON;
|
|
import com.alibaba.fastjson.JSON;
|
|
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
import com.malk.config.H3tok3Config;
|
|
import com.malk.config.H3tok3Config;
|
|
|
|
|
+import com.malk.model.SyncResult;
|
|
|
import com.malk.model.WangdianResponse;
|
|
import com.malk.model.WangdianResponse;
|
|
|
|
|
+import cn.wangdian.erp.sdk.Client;
|
|
|
|
|
+import cn.wangdian.erp.sdk.WdtErpException;
|
|
|
|
|
+import cn.wangdian.erp.sdk.api.wms.StockAPI;
|
|
|
|
|
+import cn.wangdian.erp.sdk.api.wms.dto.PdOrderCreateRequest;
|
|
|
|
|
+import cn.wangdian.erp.sdk.impl.ApiFactory;
|
|
|
|
|
+import cn.wangdian.erp.sdk.impl.DefaultClient;
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
@@ -13,24 +22,24 @@ import java.util.*;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 旺店通 API 客户端服务
|
|
* 旺店通 API 客户端服务
|
|
|
- *
|
|
|
|
|
|
|
+ *
|
|
|
* 功能说明:提供旺店通ERP系统的API调用能力,实现数据从氚云同步至旺店通
|
|
* 功能说明:提供旺店通ERP系统的API调用能力,实现数据从氚云同步至旺店通
|
|
|
- *
|
|
|
|
|
|
|
+ *
|
|
|
* 对接方式:API调用(盘盈盘亏单)
|
|
* 对接方式:API调用(盘盈盘亏单)
|
|
|
* 认证方式:MD5签名认证
|
|
* 认证方式:MD5签名认证
|
|
|
- *
|
|
|
|
|
|
|
+ *
|
|
|
* API地址:https://api.wangdian.cn/openapi2/
|
|
* API地址:https://api.wangdian.cn/openapi2/
|
|
|
- *
|
|
|
|
|
|
|
+ *
|
|
|
* 签名算法:
|
|
* 签名算法:
|
|
|
* 1. 将所有参数(除sign外)按key升序排列
|
|
* 1. 将所有参数(除sign外)按key升序排列
|
|
|
* 2. 拼接成字符串:key1value1key2value2... + secret
|
|
* 2. 拼接成字符串:key1value1key2value2... + secret
|
|
|
* 3. MD5加密后转大写
|
|
* 3. MD5加密后转大写
|
|
|
- *
|
|
|
|
|
|
|
+ *
|
|
|
* 接口清单(需求文档3.3.1):
|
|
* 接口清单(需求文档3.3.1):
|
|
|
* | 业务对象 | 源系统 | 目标接口 | 方法 | 核心字段映射 |
|
|
* | 业务对象 | 源系统 | 目标接口 | 方法 | 核心字段映射 |
|
|
|
* | 盘盈单 | 氚云 | stock_pd_add | POST | 仓库编码->warehouse_no, 物料编码->spec_no, 盈余数量->num, order_type->1 |
|
|
* | 盘盈单 | 氚云 | stock_pd_add | POST | 仓库编码->warehouse_no, 物料编码->spec_no, 盈余数量->num, order_type->1 |
|
|
|
* | 盘亏单 | 氚云 | stock_pd_add | POST | 仓库编码->warehouse_no, 物料编码->spec_no, 亏损数量->num, order_type->2 |
|
|
* | 盘亏单 | 氚云 | stock_pd_add | POST | 仓库编码->warehouse_no, 物料编码->spec_no, 亏损数量->num, order_type->2 |
|
|
|
- *
|
|
|
|
|
|
|
+ *
|
|
|
* 使用场景:
|
|
* 使用场景:
|
|
|
* - 氚云盘点表单审核后,根据差异结果同步至旺店通ERP
|
|
* - 氚云盘点表单审核后,根据差异结果同步至旺店通ERP
|
|
|
* - 盘盈单调整库存增加,盘亏单调整库存减少
|
|
* - 盘盈单调整库存增加,盘亏单调整库存减少
|
|
@@ -50,16 +59,19 @@ public class WangdianService {
|
|
|
/** 盘亏单类型 */
|
|
/** 盘亏单类型 */
|
|
|
public static final int ORDER_TYPE_LOSS = 2;
|
|
public static final int ORDER_TYPE_LOSS = 2;
|
|
|
|
|
|
|
|
|
|
+ /** SDK 客户端(懒加载缓存) */
|
|
|
|
|
+ private volatile StockAPI stockAPI;
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 创建盘盈盘亏单(单个物料)
|
|
* 创建盘盈盘亏单(单个物料)
|
|
|
*/
|
|
*/
|
|
|
- public WangdianResponse stockPdAdd(String warehouseNo, Integer orderType,
|
|
|
|
|
|
|
+ public WangdianResponse stockPdAdd(String warehouseNo, Integer orderType,
|
|
|
String specNo, Integer num, String remark) {
|
|
String specNo, Integer num, String remark) {
|
|
|
try {
|
|
try {
|
|
|
TreeMap<String, Object> params = new TreeMap<>();
|
|
TreeMap<String, Object> params = new TreeMap<>();
|
|
|
params.put("warehouse_no", warehouseNo);
|
|
params.put("warehouse_no", warehouseNo);
|
|
|
params.put("order_type", orderType);
|
|
params.put("order_type", orderType);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Map<String, Object> item = new HashMap<>();
|
|
Map<String, Object> item = new HashMap<>();
|
|
|
item.put("spec_no", specNo);
|
|
item.put("spec_no", specNo);
|
|
|
item.put("num", num);
|
|
item.put("num", num);
|
|
@@ -69,9 +81,9 @@ public class WangdianService {
|
|
|
params.put("items", JSON.toJSONString(new Object[]{item}));
|
|
params.put("items", JSON.toJSONString(new Object[]{item}));
|
|
|
|
|
|
|
|
String response = execute("stock_pd_add", params);
|
|
String response = execute("stock_pd_add", params);
|
|
|
- log.info("旺店通盘盈盘亏单, warehouseNo: {}, orderType: {}, 响应: {}",
|
|
|
|
|
|
|
+ log.info("旺店通盘盈盘亏单, warehouseNo: {}, orderType: {}, 响应: {}",
|
|
|
warehouseNo, orderType, response);
|
|
warehouseNo, orderType, response);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return JSON.parseObject(response, WangdianResponse.class);
|
|
return JSON.parseObject(response, WangdianResponse.class);
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("旺店通盘盈盘亏单异常, warehouseNo: {}, orderType: {}", warehouseNo, orderType, e);
|
|
log.error("旺店通盘盈盘亏单异常, warehouseNo: {}, orderType: {}", warehouseNo, orderType, e);
|
|
@@ -134,7 +146,7 @@ public class WangdianService {
|
|
|
*/
|
|
*/
|
|
|
private String execute(String method, Map<String, Object> params) {
|
|
private String execute(String method, Map<String, Object> params) {
|
|
|
long timestamp = System.currentTimeMillis();
|
|
long timestamp = System.currentTimeMillis();
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 构建签名前的参数(包含认证参数和业务参数)
|
|
// 构建签名前的参数(包含认证参数和业务参数)
|
|
|
TreeMap<String, Object> signParams = new TreeMap<>();
|
|
TreeMap<String, Object> signParams = new TreeMap<>();
|
|
|
signParams.put("appkey", config.getWangdian().getAppkey());
|
|
signParams.put("appkey", config.getWangdian().getAppkey());
|
|
@@ -143,7 +155,7 @@ public class WangdianService {
|
|
|
signParams.put("timestamp", timestamp);
|
|
signParams.put("timestamp", timestamp);
|
|
|
signParams.put("v", 1);
|
|
signParams.put("v", 1);
|
|
|
signParams.putAll(params);
|
|
signParams.putAll(params);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 生成签名
|
|
// 生成签名
|
|
|
String sign = generateSign(signParams);
|
|
String sign = generateSign(signParams);
|
|
|
|
|
|
|
@@ -172,7 +184,7 @@ public class WangdianService {
|
|
|
}
|
|
}
|
|
|
// 追加密钥
|
|
// 追加密钥
|
|
|
sb.append(config.getWangdian().getSecret());
|
|
sb.append(config.getWangdian().getSecret());
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
|
|
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
|
|
|
byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
|
|
byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
|
|
@@ -189,4 +201,167 @@ public class WangdianService {
|
|
|
throw new RuntimeException("MD5签名异常", e);
|
|
throw new RuntimeException("MD5签名异常", e);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== SDK(openapi v1.0)方式调用 ====================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取SDK的StockAPI代理对象(懒加载,线程安全)
|
|
|
|
|
+ */
|
|
|
|
|
+ private StockAPI getStockAPI() {
|
|
|
|
|
+ if (stockAPI == null) {
|
|
|
|
|
+ synchronized (this) {
|
|
|
|
|
+ if (stockAPI == null) {
|
|
|
|
|
+ H3tok3Config.WangdianConfig wc = config.getWangdian();
|
|
|
|
|
+ log.info("初始化旺店通SDK客户端, sid: {}, erpUrl: {}, erpKey: {}", wc.getSid(), wc.getErpUrl(), wc.getErpKey());
|
|
|
|
|
+ Client client = DefaultClient.get(wc.getSid(), wc.getErpUrl(), wc.getErpKey(), wc.getErpSecret());
|
|
|
|
|
+ stockAPI = ApiFactory.get(client, StockAPI.class);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return stockAPI;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 通过SDK调用 wms.StockPd.stockSyncByPd 接口创建盘点单
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param warehouseNo 仓库编号
|
|
|
|
|
+ * @param remark 盘点单备注
|
|
|
|
|
+ * @param detailList 盘点明细列表
|
|
|
|
|
+ * @return 旺店通返回结果(包含盘点单号等信息)
|
|
|
|
|
+ */
|
|
|
|
|
+ public Map<String, Object> createPdOrderBySdk(String warehouseNo, String remark,
|
|
|
|
|
+ List<PdOrderCreateRequest.DetailDto> detailList) {
|
|
|
|
|
+ PdOrderCreateRequest.OrderDto orderDto = new PdOrderCreateRequest.OrderDto();
|
|
|
|
|
+ orderDto.setWarehouseNo(warehouseNo);
|
|
|
|
|
+ orderDto.setDefectMode(PdOrderCreateRequest.OrderDto.DEFECT_MODE_NORMAL);
|
|
|
|
|
+ orderDto.setRemark(remark);
|
|
|
|
|
+
|
|
|
|
|
+ log.info("SDK创建盘点单, warehouseNo: {}, remark: {}, detailCount: {}", warehouseNo, remark, detailList.size());
|
|
|
|
|
+ Map<String, Object> result = getStockAPI().createPdOrder(orderDto, detailList);
|
|
|
|
|
+ log.info("SDK创建盘点单响应: {}", result);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从氚云数据映射并同步盘点单到旺店通(通过SDK)
|
|
|
|
|
+ *
|
|
|
|
|
+ * 功能说明:
|
|
|
|
|
+ * 1. 从氚云盘点表单JSON中提取仓库编码、明细行数据
|
|
|
|
|
+ * 2. 映射为旺店通 PdOrderCreateRequest 结构
|
|
|
|
|
+ * 3. 调用SDK的 wms.StockPd.stockSyncByPd 接口创建盘点单
|
|
|
|
|
+ *
|
|
|
|
|
+ * 氚云字段映射(请根据实际氚云表单字段编码调整):
|
|
|
|
|
+ * | 氚云字段 | 旺店通字段 | 说明 |
|
|
|
|
|
+ * | F_WAREHOUSE_NO | OrderDto.warehouseNo | 仓库编号 |
|
|
|
|
|
+ * | F_REMARK | OrderDto.remark | 盘点单备注 |
|
|
|
|
|
+ * | 明细.F_SPEC_NO | DetailDto.specNo | 商家编码/物料编码 |
|
|
|
|
|
+ * | 明细.F_NEW_NUM | DetailDto.newNum | 盘点数量 |
|
|
|
|
|
+ * | 明细.F_DEFECT | DetailDto.defect | 是否残次品 |
|
|
|
|
|
+ * | 明细.F_DETAIL_REMARK | DetailDto.remark | 明细备注 |
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param bizData 氚云业务对象数据
|
|
|
|
|
+ * @param schemaCode 氚云表单编码(用于识别明细子表key)
|
|
|
|
|
+ * @return 同步结果
|
|
|
|
|
+ */
|
|
|
|
|
+ public SyncResult syncPdOrderFromH3yun(JSONObject bizData, String schemaCode) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // TODO: 请根据实际氚云盘点表单字段编码调整以下常量
|
|
|
|
|
+ // 主表字段编码
|
|
|
|
|
+ String FIELD_WAREHOUSE_NO = "F0000068"; // 仓库编号
|
|
|
|
|
+ String FIELD_REMARK = "F0000069"; // 盘点备注
|
|
|
|
|
+ // 明细子表字段编码
|
|
|
|
|
+ String DETAIL_FIELD_SPEC_NO = "F0000028"; // 物料编码/商家编码
|
|
|
|
|
+ String DETAIL_FIELD_NEW_NUM = "F0000064"; // 盘点数量
|
|
|
|
|
+ String DETAIL_FIELD_DEFECT = "F0000070"; // 是否残次品
|
|
|
|
|
+ String DETAIL_FIELD_REMARK = "F0000004"; // 明细备注
|
|
|
|
|
+ // 1. 提取主表数据
|
|
|
|
|
+ String warehouseNo = bizData.getString(FIELD_WAREHOUSE_NO);
|
|
|
|
|
+ String remark = bizData.getString(FIELD_REMARK);
|
|
|
|
|
+
|
|
|
|
|
+ // 盘点物料明细
|
|
|
|
|
+ JSONArray billentryArray = bizData.getJSONArray("D293655safw79e8odkyyvqessgs9");
|
|
|
|
|
+ // 3. 映射为旺店通盘点单明细
|
|
|
|
|
+ List<PdOrderCreateRequest.DetailDto> detailList = new ArrayList<>();
|
|
|
|
|
+ for (int i = 0; i < billentryArray.size(); i++) {
|
|
|
|
|
+ JSONObject detail = billentryArray.getJSONObject(i);
|
|
|
|
|
+ PdOrderCreateRequest.DetailDto detailDto = new PdOrderCreateRequest.DetailDto();
|
|
|
|
|
+
|
|
|
|
|
+ String specNo = detail.getString(DETAIL_FIELD_SPEC_NO);
|
|
|
|
|
+ if (specNo == null || specNo.trim().isEmpty()) {
|
|
|
|
|
+ log.warn("第{}行明细物料编码为空, 跳过", i + 1);
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ detailDto.setSpecNo(specNo);
|
|
|
|
|
+
|
|
|
|
|
+ String newNum = detail.getString(DETAIL_FIELD_NEW_NUM);
|
|
|
|
|
+ if (newNum == null) {
|
|
|
|
|
+ newNum = "0";
|
|
|
|
|
+ }
|
|
|
|
|
+ detailDto.setNewNum(newNum);
|
|
|
|
|
+
|
|
|
|
|
+ String defect = detail.getString(DETAIL_FIELD_DEFECT);
|
|
|
|
|
+ defect = defect == "是" ? "true" : "false";
|
|
|
|
|
+ detailDto.setDefect(Boolean.valueOf(defect));
|
|
|
|
|
+
|
|
|
|
|
+ detailDto.setRemark(detail.getString(DETAIL_FIELD_REMARK));
|
|
|
|
|
+
|
|
|
|
|
+ detailList.add(detailDto);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (detailList.isEmpty()) {
|
|
|
|
|
+ return SyncResult.fail("映射后明细行为空, 无有效物料编码");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 调用SDK创建盘点单
|
|
|
|
|
+ Map<String, Object> response = createPdOrderBySdk(warehouseNo, remark, detailList);
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 解析结果
|
|
|
|
|
+ // SDK返回的Map中通常包含盘点单号等信息
|
|
|
|
|
+ String pdNo = null;
|
|
|
|
|
+ if (response != null) {
|
|
|
|
|
+ pdNo = response.get("pd_no") != null ? response.get("pd_no").toString() : null;
|
|
|
|
|
+ if (pdNo == null) {
|
|
|
|
|
+ pdNo = response.get("order_no") != null ? response.get("order_no").toString() : null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ log.info("氚云数据同步盘点单到旺店通成功, warehouseNo: {}, pdNo: {}", warehouseNo, pdNo);
|
|
|
|
|
+ return SyncResult.success("同步成功", pdNo);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ String errorMsg = e.getMessage();
|
|
|
|
|
+ if (e.getCause() instanceof WdtErpException) {
|
|
|
|
|
+ WdtErpException wdtEx = (WdtErpException) e.getCause();
|
|
|
|
|
+ errorMsg = "旺店通返回错误[code=" + wdtEx.getCode() + "]: " + wdtEx.getMessage();
|
|
|
|
|
+ log.error("SDK调用旺店通盘点单异常, 错误码: {}, 消息: {}", wdtEx.getCode(), wdtEx.getMessage(), e);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ log.error("同步盘点单到旺店通异常", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return SyncResult.fail(errorMsg);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从氚云业务对象中查找明细子表数据
|
|
|
|
|
+ * 氚云的明细行数据通常以子表SchemaCode为key存储为JSONArray
|
|
|
|
|
+ */
|
|
|
|
|
+ private JSONArray findDetailArray(JSONObject bizData, String schemaCode) {
|
|
|
|
|
+ // 优先用schemaCode作为key查找
|
|
|
|
|
+ JSONArray arr = bizData.getJSONArray(schemaCode);
|
|
|
|
|
+ if (arr != null && !arr.isEmpty()) {
|
|
|
|
|
+ return arr;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 遍历所有字段,找到第一个非空的JSONArray作为明细
|
|
|
|
|
+ for (String key : bizData.keySet()) {
|
|
|
|
|
+ Object val = bizData.get(key);
|
|
|
|
|
+ if (val instanceof JSONArray) {
|
|
|
|
|
+ JSONArray jsonArray = (JSONArray) val;
|
|
|
|
|
+ if (!jsonArray.isEmpty() && jsonArray.getJSONObject(0) != null) {
|
|
|
|
|
+ log.info("通过key: {} 找到明细子表数据, 共{}行", key, jsonArray.size());
|
|
|
|
|
+ return jsonArray;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|