|
|
@@ -43,11 +43,12 @@ import java.util.List;
|
|
|
import java.util.Map;
|
|
|
|
|
|
/**
|
|
|
- * 设备设备层模版管理服务实现类
|
|
|
- * 提供设备层模版的完整业务逻辑实现,包括CRUD和同步功能
|
|
|
+ * 设备模版管理服务实现类
|
|
|
+ * 支持层模版(LAYER)和柜模版(CABINET)两种类型
|
|
|
+ * 提供设备模版的CRUD和同步功能,同步时自动检测模版类型
|
|
|
*
|
|
|
* @author haha-service
|
|
|
- * @version 1.0.0
|
|
|
+ * @version 2.0.0
|
|
|
*/
|
|
|
@Slf4j
|
|
|
@Service
|
|
|
@@ -78,9 +79,9 @@ public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, L
|
|
|
* @return 分页结果(已填充展示标签)
|
|
|
*/
|
|
|
@Override
|
|
|
- public IPage<LayerTemplate> getPage(int page, int pageSize, String deviceId, Integer syncStatus, String templateName) {
|
|
|
- log.info("分页查询层模版: page={}, pageSize={}, deviceId={}, syncStatus={}, templateName={}",
|
|
|
- page, pageSize, deviceId, syncStatus, templateName);
|
|
|
+ public IPage<LayerTemplate> getPage(int page, int pageSize, String deviceId, Integer syncStatus, String templateName, String templateType) {
|
|
|
+ log.info("分页查询设备模版: page={}, pageSize={}, deviceId={}, syncStatus={}, templateName={}, templateType={}",
|
|
|
+ page, pageSize, deviceId, syncStatus, templateName, templateType);
|
|
|
|
|
|
LambdaQueryWrapper<LayerTemplate> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
|
@@ -88,13 +89,14 @@ public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, L
|
|
|
wrapper.like(deviceId != null && !deviceId.isEmpty(), LayerTemplate::getDeviceId, deviceId)
|
|
|
.eq(syncStatus != null, LayerTemplate::getSyncStatus, syncStatus)
|
|
|
.like(templateName != null && !templateName.isEmpty(), LayerTemplate::getTemplateName, templateName)
|
|
|
+ .eq(templateType != null && !templateType.isEmpty(), LayerTemplate::getTemplateType, templateType)
|
|
|
.eq(LayerTemplate::getIsDeleted, CommonConstants.NOT_DELETED)
|
|
|
.orderByDesc(LayerTemplate::getCreateTime);
|
|
|
|
|
|
// 执行分页查询
|
|
|
IPage<LayerTemplate> pageResult = this.page(new Page<>(page, pageSize), wrapper);
|
|
|
|
|
|
- log.info("层模版查询结果: total={}, records={}", pageResult.getTotal(), pageResult.getRecords().size());
|
|
|
+ log.info("设备模版查询结果: total={}, records={}", pageResult.getTotal(), pageResult.getRecords().size());
|
|
|
|
|
|
// 填充展示字段(同步状态标签、设备类型等)
|
|
|
pageResult.getRecords().forEach(this::fillLabels);
|
|
|
@@ -213,6 +215,9 @@ public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, L
|
|
|
if (dto.getTemplateName() != null) {
|
|
|
existing.setTemplateName(dto.getTemplateName());
|
|
|
}
|
|
|
+ if (dto.getTemplateType() != null) {
|
|
|
+ existing.setTemplateType(dto.getTemplateType());
|
|
|
+ }
|
|
|
if (dto.getLeftFloors() != null && !dto.getLeftFloors().isEmpty()) {
|
|
|
try {
|
|
|
existing.setLeftFloors(objectMapper.writeValueAsString(dto.getLeftFloors()));
|
|
|
@@ -318,14 +323,52 @@ public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, L
|
|
|
updateSyncStatus(deviceId, SyncConstants.STATUS_SYNCING, null);
|
|
|
|
|
|
try {
|
|
|
- // 步骤2: 调用哈哈平台API获取层模版数据
|
|
|
+ // 步骤2: 自动检测模版类型并调用对应API
|
|
|
GoodsApi goodsApi = hahaClient.getGoodsApi();
|
|
|
- Map<String, Object> apiData = goodsApi.getLayerTemplate(deviceId);
|
|
|
+ Map<String, Object> apiData = null;
|
|
|
+ String detectedTemplateType = null;
|
|
|
+
|
|
|
+ // 先尝试层模版 API (4.5.1),失败或数据无效则尝试柜模版 API (4.1)
|
|
|
+ // 注意:BaseApi.post() 只在 code != 1 时抛 HahaException
|
|
|
+ // 如果平台对柜模版设备返回 code=1 但 data 为空,抛异常也不会触发
|
|
|
+ // 所以必须同时验证返回数据是否包含层模版特有字段
|
|
|
+ boolean layerValid = false;
|
|
|
+ try {
|
|
|
+ apiData = goodsApi.getLayerTemplate(deviceId);
|
|
|
+ // 验证返回数据是否为有效的层模版
|
|
|
+ // products 必须是 Map 且非空,不能是空字符串(柜模版设备会返回 products:"")
|
|
|
+ layerValid = apiData != null
|
|
|
+ && apiData.get("products") instanceof Map
|
|
|
+ && !((Map<?, ?>) apiData.get("products")).isEmpty();
|
|
|
+ if (layerValid) {
|
|
|
+ detectedTemplateType = SyncConstants.TEMPLATE_TYPE_LAYER;
|
|
|
+ log.info("设备 {} 检测为层模版(LAYER)类型,数据: {}", deviceId, apiData);
|
|
|
+ } else {
|
|
|
+ log.info("层模版API返回成功但数据为空/无效(缺少products字段),降级为柜模版: deviceId={}", deviceId);
|
|
|
+ }
|
|
|
+ } catch (Exception layerEx) {
|
|
|
+ log.info("层模版API调用失败(code!=1),尝试柜模版API: deviceId={}, error={}", deviceId, layerEx.getMessage());
|
|
|
+ }
|
|
|
|
|
|
- log.info("成功获取平台层模版数据: deviceId={}, data={}", deviceId, apiData);
|
|
|
+ if (!layerValid) {
|
|
|
+ try {
|
|
|
+ apiData = goodsApi.getCabinetTemplate(deviceId);
|
|
|
+ detectedTemplateType = SyncConstants.TEMPLATE_TYPE_CABINET;
|
|
|
+ log.info("设备 {} 检测为柜模版(CABINET)类型,数据: {}", deviceId, apiData);
|
|
|
+ } catch (Exception cabinetEx) {
|
|
|
+ log.error("柜模版API也调用失败: deviceId={}, error={}", deviceId, cabinetEx.getMessage());
|
|
|
+ throw new BusinessException(500, "无法获取设备模版数据,层模版和柜模版API均失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // 步骤3-4: 解析返回数据并转换为实体对象
|
|
|
- LayerTemplate updatedTemplate = convertFromApiData(deviceId, apiData);
|
|
|
+ // 步骤3-4: 根据模版类型解析数据并转换为实体对象
|
|
|
+ LayerTemplate updatedTemplate;
|
|
|
+ if (SyncConstants.TEMPLATE_TYPE_CABINET.equals(detectedTemplateType)) {
|
|
|
+ updatedTemplate = convertCabinetFromApiData(deviceId, apiData);
|
|
|
+ } else {
|
|
|
+ updatedTemplate = convertFromApiData(deviceId, apiData);
|
|
|
+ }
|
|
|
+ updatedTemplate.setTemplateType(detectedTemplateType);
|
|
|
|
|
|
// 步骤5: 更新或创建本地记录
|
|
|
if (isNewRecord) {
|
|
|
@@ -337,11 +380,13 @@ public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, L
|
|
|
localTemplate.setLeftFloors(updatedTemplate.getLeftFloors());
|
|
|
localTemplate.setRightFloors(updatedTemplate.getRightFloors());
|
|
|
localTemplate.setGoodsRule(updatedTemplate.getGoodsRule());
|
|
|
+ localTemplate.setTemplateType(detectedTemplateType);
|
|
|
localTemplate.setSyncTime(LocalDateTime.now());
|
|
|
localTemplate.setUpdateTime(LocalDateTime.now());
|
|
|
|
|
|
this.save(localTemplate);
|
|
|
- log.info("新建层模版记录: id={}, deviceId={}", localTemplate.getId(), deviceId);
|
|
|
+ log.info("新建设备模版记录: id={}, deviceId={}, templateType={}",
|
|
|
+ localTemplate.getId(), deviceId, detectedTemplateType);
|
|
|
} else {
|
|
|
// 已存在记录,更新数据
|
|
|
LambdaUpdateWrapper<LayerTemplate> updateWrapper = new LambdaUpdateWrapper<>();
|
|
|
@@ -353,10 +398,11 @@ public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, L
|
|
|
.set(LayerTemplate::getLeftFloors, updatedTemplate.getLeftFloors())
|
|
|
.set(LayerTemplate::getRightFloors, updatedTemplate.getRightFloors())
|
|
|
.set(LayerTemplate::getGoodsRule, updatedTemplate.getGoodsRule())
|
|
|
+ .set(LayerTemplate::getTemplateType, detectedTemplateType)
|
|
|
.set(LayerTemplate::getUpdateTime, LocalDateTime.now());
|
|
|
|
|
|
this.update(updateWrapper);
|
|
|
- log.info("更新层模版记录: deviceId={}", deviceId);
|
|
|
+ log.info("更新设备模版记录: deviceId={}, templateType={}", deviceId, detectedTemplateType);
|
|
|
}
|
|
|
|
|
|
// 步骤6: 从层模版数据初始化库存(仅对尚不存在的商品创建 stock=0 记录)
|
|
|
@@ -376,7 +422,7 @@ public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, L
|
|
|
// 步骤7: 更新同步状态为"已同步"并记录时间
|
|
|
updateSyncStatus(deviceId, SyncConstants.STATUS_SYNCED, LocalDateTime.now());
|
|
|
|
|
|
- log.info("层模版同步成功: deviceId={}", deviceId);
|
|
|
+ log.info("设备模版同步成功: deviceId={}, templateType={}", deviceId, detectedTemplateType);
|
|
|
return true;
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
@@ -526,6 +572,79 @@ public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, L
|
|
|
return template;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 将柜模版 API (4.1) 返回数据转换为实体对象
|
|
|
+ * 柜模版没有层的概念,将4.1返回的分类商品列表展平为单层结构
|
|
|
+ *
|
|
|
+ * @param deviceId 设备ID
|
|
|
+ * @param apiData API返回的柜模版数据
|
|
|
+ * @return 转换后的实体对象
|
|
|
+ */
|
|
|
+ private LayerTemplate convertCabinetFromApiData(String deviceId, Map<String, Object> apiData) {
|
|
|
+ LayerTemplate template = new LayerTemplate();
|
|
|
+ template.setDeviceId(deviceId);
|
|
|
+ template.setTemplateType(SyncConstants.TEMPLATE_TYPE_CABINET);
|
|
|
+
|
|
|
+ // 柜模版无层数概念
|
|
|
+ template.setShelfNum(null);
|
|
|
+
|
|
|
+ // 将 dataInfo 中的所有商品展平为单个虚拟层
|
|
|
+ List<FloorConfig> floors = new ArrayList<>();
|
|
|
+ FloorConfig singleFloor = new FloorConfig();
|
|
|
+ singleFloor.setFloor(1);
|
|
|
+ List<GoodsItem> goodsList = new ArrayList<>();
|
|
|
+
|
|
|
+ if (apiData.containsKey("dataInfo")) {
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ List<Map<String, Object>> dataInfoList = (List<Map<String, Object>>) apiData.get("dataInfo");
|
|
|
+ if (dataInfoList != null) {
|
|
|
+ for (Map<String, Object> category : dataInfoList) {
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ List<Map<String, Object>> goods = (List<Map<String, Object>>) category.get("goods");
|
|
|
+ if (goods != null) {
|
|
|
+ for (Map<String, Object> item : goods) {
|
|
|
+ GoodsItem goodsItem = new GoodsItem();
|
|
|
+ goodsItem.setKey(String.valueOf(item.getOrDefault("code", "")));
|
|
|
+ goodsItem.setLabel(String.valueOf(item.getOrDefault("name", "")));
|
|
|
+ goodsItem.setPic(String.valueOf(item.getOrDefault("pic", "")));
|
|
|
+ goodsItem.setRec_pic(String.valueOf(item.getOrDefault("rec_pic", "")));
|
|
|
+ Object cPrice = item.get("c_price");
|
|
|
+ if (cPrice != null) {
|
|
|
+ goodsItem.setC_price(String.valueOf(cPrice));
|
|
|
+ }
|
|
|
+ Object discount = item.get("discount");
|
|
|
+ if (discount != null) {
|
|
|
+ goodsItem.setDiscount(String.valueOf(discount));
|
|
|
+ }
|
|
|
+ Object disPrice = item.get("dis_price");
|
|
|
+ if (disPrice != null && !String.valueOf(disPrice).isEmpty()) {
|
|
|
+ goodsItem.setDis_price(String.valueOf(disPrice));
|
|
|
+ }
|
|
|
+ goodsItem.setType(String.valueOf(item.getOrDefault("type", "")));
|
|
|
+ goodsItem.setStock(0); // 柜模版中stock始终为0,由实际库存决定
|
|
|
+ goodsList.add(goodsItem);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ singleFloor.setGoods(goodsList);
|
|
|
+ floors.add(singleFloor);
|
|
|
+
|
|
|
+ try {
|
|
|
+ template.setLeftFloors(objectMapper.writeValueAsString(floors));
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ log.error("柜模版左门层数据JSON序列化失败: deviceId={}", deviceId, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 柜模版没有右门
|
|
|
+ template.setRightFloors(null);
|
|
|
+
|
|
|
+ log.info("柜模版数据转换完成: deviceId={}, 商品数={}", deviceId, goodsList.size());
|
|
|
+ return template;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 将前端DTO转换为提交给平台的JSON格式
|
|
|
* 用于将本地修改后的层模版数据推送到哈哈平台
|
|
|
@@ -562,6 +681,7 @@ public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, L
|
|
|
LayerTemplate template = new LayerTemplate();
|
|
|
template.setDeviceId(dto.getDeviceId());
|
|
|
template.setTemplateName(dto.getTemplateName());
|
|
|
+ template.setTemplateType(dto.getTemplateType() != null ? dto.getTemplateType() : SyncConstants.TEMPLATE_TYPE_LAYER);
|
|
|
|
|
|
// 序列化左门层数据
|
|
|
if (dto.getLeftFloors() != null && !dto.getLeftFloors().isEmpty()) {
|
|
|
@@ -607,6 +727,11 @@ public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, L
|
|
|
template.setDeviceTypeName(getDeviceTypeName(template.getDeviceType()));
|
|
|
}
|
|
|
|
|
|
+ // 2.5. 模版类型标签
|
|
|
+ if (template.getTemplateType() != null) {
|
|
|
+ template.setTemplateTypeLabel(SyncConstants.getTemplateTypeDesc(template.getTemplateType()));
|
|
|
+ }
|
|
|
+
|
|
|
// 3. 左门层数据展示(美化JSON显示)
|
|
|
if (template.getLeftFloors() != null && !template.getLeftFloors().isEmpty()) {
|
|
|
template.setLeftFloorsDisplay(formatJsonForDisplay(template.getLeftFloors()));
|
|
|
@@ -918,25 +1043,37 @@ public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, L
|
|
|
|
|
|
update(template.getId(), dto);
|
|
|
|
|
|
- // 推送层模板到哈哈平台
|
|
|
- pushLayerTemplateToHaha(deviceId, leftFloors);
|
|
|
+ // 推送模版到哈哈平台(根据模版类型路由到不同API)
|
|
|
+ pushTemplateToHaha(deviceId, leftFloors);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 推送层模板配置到哈哈平台
|
|
|
+ * 推送模版配置到哈哈平台
|
|
|
+ * 根据设备的模版类型自动选择对应API:
|
|
|
+ * - LAYER层模版: 使用 4.5.2 (postLayerTypeInfo)
|
|
|
+ * - CABINET柜模版: 使用 4.5 (systemtemplate/deviceGoods)
|
|
|
*/
|
|
|
- private void pushLayerTemplateToHaha(String deviceId, List<FloorConfig> leftFloors) {
|
|
|
+ private void pushTemplateToHaha(String deviceId, List<FloorConfig> leftFloors) {
|
|
|
try {
|
|
|
- Map<String, Object> products = new HashMap<>();
|
|
|
- products.put("left", leftFloors);
|
|
|
- products.put("right", List.of());
|
|
|
- String productsJson = objectMapper.writeValueAsString(products);
|
|
|
- hahaClient.getGoodsApi().updateLayerTemplate(deviceId, productsJson);
|
|
|
+ LayerTemplate template = getByDeviceId(deviceId);
|
|
|
+ if (template != null && SyncConstants.TEMPLATE_TYPE_CABINET.equals(template.getTemplateType())) {
|
|
|
+ // 柜模版: 提取所有商品编码,使用 4.5 API 覆盖更新
|
|
|
+ String goodsCodes = extractProductCodesFromFloors(leftFloors);
|
|
|
+ hahaClient.getGoodsApi().updateCabinetTemplate(deviceId, goodsCodes, "COVER");
|
|
|
+ log.info("柜模版已同步到哈哈平台 - deviceId: {}, goodsCodes: {}", deviceId, goodsCodes);
|
|
|
+ } else {
|
|
|
+ // 层模版: 使用 4.5.2 API — 原有逻辑
|
|
|
+ Map<String, Object> products = new HashMap<>();
|
|
|
+ products.put("left", leftFloors);
|
|
|
+ products.put("right", List.of());
|
|
|
+ String productsJson = objectMapper.writeValueAsString(products);
|
|
|
+ hahaClient.getGoodsApi().updateLayerTemplate(deviceId, productsJson);
|
|
|
+ log.info("层模版已同步到哈哈平台 - deviceId: {}", deviceId);
|
|
|
+ }
|
|
|
updateSyncStatus(deviceId, SyncConstants.STATUS_SYNCED, LocalDateTime.now());
|
|
|
- log.info("层模板已同步到哈哈平台 - deviceId: {}", deviceId);
|
|
|
} catch (Exception e) {
|
|
|
- log.error("推送层模板到哈哈平台失败 - deviceId: {}, error: {}", deviceId, e.getMessage(), e);
|
|
|
+ log.error("推送模版到哈哈平台失败 - deviceId: {}, error: {}", deviceId, e.getMessage(), e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -971,26 +1108,47 @@ public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, L
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 从层模版API数据中提取商品编码,为不存在的商品创建库存记录(stock=0)
|
|
|
+ * 从模版API数据中提取商品编码,为不存在的商品创建库存记录(stock=0)
|
|
|
+ * 支持层模版(4.5.1)和柜模版(4.1)两种数据格式
|
|
|
* 仅在库存记录不存在时创建,已存在的记录不受影响
|
|
|
*/
|
|
|
private void initInventoryFromTemplate(String deviceId, Map<String, Object> apiData) {
|
|
|
// 提取所有商品编码
|
|
|
java.util.Set<String> productCodes = new java.util.HashSet<>();
|
|
|
|
|
|
- if (apiData.containsKey("products")) {
|
|
|
+ // 层模版格式: products.left / products.right(必须 instanceof Map,柜模版可能返回 products:"")
|
|
|
+ if (apiData.containsKey("products") && apiData.get("products") instanceof Map) {
|
|
|
@SuppressWarnings("unchecked")
|
|
|
Map<String, Object> products = (Map<String, Object>) apiData.get("products");
|
|
|
extractCodesFromFloors(products.get("left"), productCodes);
|
|
|
extractCodesFromFloors(products.get("right"), productCodes);
|
|
|
}
|
|
|
+ // 柜模版格式: dataInfo[].goods[].code
|
|
|
+ else if (apiData.containsKey("dataInfo")) {
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ List<Map<String, Object>> dataInfoList = (List<Map<String, Object>>) apiData.get("dataInfo");
|
|
|
+ if (dataInfoList != null) {
|
|
|
+ for (Map<String, Object> category : dataInfoList) {
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ List<Map<String, Object>> goods = (List<Map<String, Object>>) category.get("goods");
|
|
|
+ if (goods != null) {
|
|
|
+ for (Map<String, Object> item : goods) {
|
|
|
+ Object code = item.get("code");
|
|
|
+ if (code != null && !code.toString().isEmpty()) {
|
|
|
+ productCodes.add(code.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
if (productCodes.isEmpty()) {
|
|
|
- log.info("层模版中无商品配置,跳过库存初始化: deviceId={}", deviceId);
|
|
|
+ log.info("设备模版中无商品配置,跳过库存初始化: deviceId={}", deviceId);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- log.info("从层模版提取到 {} 个商品编码,开始初始化库存: deviceId={}", productCodes.size(), deviceId);
|
|
|
+ log.info("从设备模版提取到 {} 个商品编码,开始初始化库存: deviceId={}", productCodes.size(), deviceId);
|
|
|
int created = 0;
|
|
|
int skipped = 0;
|
|
|
|
|
|
@@ -1057,4 +1215,24 @@ public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, L
|
|
|
log.warn("解析层商品数据失败: {}", e.getMessage());
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从 FloorConfig 列表中提取所有商品编码,用逗号拼接
|
|
|
+ * 用于柜模版更新时传递给 4.5 API
|
|
|
+ */
|
|
|
+ private String extractProductCodesFromFloors(List<FloorConfig> floors) {
|
|
|
+ java.util.Set<String> codes = new java.util.LinkedHashSet<>();
|
|
|
+ if (floors != null) {
|
|
|
+ for (FloorConfig floor : floors) {
|
|
|
+ if (floor.getGoods() != null) {
|
|
|
+ for (GoodsItem goods : floor.getGoods()) {
|
|
|
+ if (goods.getKey() != null && !goods.getKey().isEmpty()) {
|
|
|
+ codes.add(goods.getKey());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return String.join(",", codes);
|
|
|
+ }
|
|
|
}
|