From 33715b9661a5fde5057e9c71683fe78c5bdaee29 Mon Sep 17 00:00:00 2001 From: "han\\hanst" Date: Thu, 23 Oct 2025 21:01:59 +0800 Subject: [PATCH] =?UTF-8?q?agv=E7=9C=9F=E5=AE=9E=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gaotao/common/utils/AgvClientUtil.java | 21 -- .../modules/dashboard/dao/DashboardDao.java | 99 ++++++ .../service/DashboardWebSocketService.java | 16 + .../dashboard/task/DashboardPushTask.java | 285 +++++++++++++++++- .../po/service/impl/PoServiceImpl.java | 14 +- .../mapper/dashboard/DashboardDao.xml | 238 +++++++++++++++ 6 files changed, 647 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/gaotao/common/utils/AgvClientUtil.java b/src/main/java/com/gaotao/common/utils/AgvClientUtil.java index 95fbe22..d300c86 100644 --- a/src/main/java/com/gaotao/common/utils/AgvClientUtil.java +++ b/src/main/java/com/gaotao/common/utils/AgvClientUtil.java @@ -313,39 +313,18 @@ public class AgvClientUtil { */ public Object getOnlineRobot() { String url = agvUrl + "/rpc/getOnlineRobot"; - Map request = new HashMap<>(); - - Long logId = null; try { - // 记录接口调用日志 - String requestJson = JSONObject.toJSONString(request); - logId = interfaceCallLogService.logInterfaceCall( - "AgvClientUtil", - "getOnlineRobot", - requestJson, - "55", - null, - "AGV查询在线小车接口" - ); String ifsResponse = HttpUtils.doGet(url, null, null); - ObjectMapper mapper = new ObjectMapper(); JsonNode jsonNode = mapper.readTree(ifsResponse); - int code = jsonNode.get("code").asInt(); String msg = jsonNode.get("msg").asText(); if(code != 200){ throw new RuntimeException("调用AGV接口失败,错误码:"+code+",错误信息:"+msg); } - // 返回数据部分 return jsonNode.get("data"); - } catch (Exception e) { - // 更新接口日志错误信息 - if (logId != null) { - interfaceCallLogService.updateCallResult(logId, null, "FAILED", e.getMessage(), null); - } throw new RuntimeException(e.getMessage()); } } diff --git a/src/main/java/com/gaotao/modules/dashboard/dao/DashboardDao.java b/src/main/java/com/gaotao/modules/dashboard/dao/DashboardDao.java index 0f13359..d78fecd 100644 --- a/src/main/java/com/gaotao/modules/dashboard/dao/DashboardDao.java +++ b/src/main/java/com/gaotao/modules/dashboard/dao/DashboardDao.java @@ -14,6 +14,7 @@ import java.util.Map; * * * @author System @@ -35,5 +36,103 @@ public interface DashboardDao { * @return 分切入库区实时数据 */ List> querySlittingInboundData(); + + // ==================== 智能立体仓库看板数据 ==================== + + /** + * 查询立体仓库任务统计数据 + * + *

数据说明:

+ *
    + *
  • totalTasks - 累计任务总数
  • + *
  • monthlyTasks - 月度作业总数
  • + *
  • outboundTasks - 出库作业数
  • + *
  • inboundTasks - 入库作业数
  • + *
  • outboundPercent - 出库占比
  • + *
  • inboundPercent - 入库占比
  • + *
+ * + * @return 任务统计数据 + */ + Map queryWarehouseTaskStats(); + + /** + * 查询立体仓库库位利用率数据 + * + *

数据说明:

+ *
    + *
  • totalSlots - 总库位数
  • + *
  • usedSlots - 已使用库位数
  • + *
  • utilizationRate - 利用率百分比
  • + *
  • steelPallet - 钢制托盘数量
  • + *
  • guardPallet - 护边托盘数量
  • + *
  • flatPallet - 平托盘数量
  • + *
+ * + * @return 库位利用率数据 + */ + Map queryWarehouseStorageUtilization(); + + /** + * 查询立体仓库机器人状态数据 + * + *

数据说明:

+ *
    + *
  • id - 机器人ID
  • + *
  • name - 机器人名称
  • + *
  • status - 状态(working/idle/charging/error)
  • + *
  • statusText - 状态文本
  • + *
  • efficiency - 效率百分比
  • + *
  • tasks - 当前任务数
  • + *
+ * + * @return 机器人状态列表 + */ + List> queryWarehouseRobotStatus(); + + /** + * 查询立体仓库AGV状态数据 + * + *

注意:此方法已废弃!

+ *

AGV状态数据不从数据库查询,而是从TUSK系统实时获取。

+ *

请使用 DashboardPushTask.getAgvStatusFromTusk() 方法获取AGV状态。

+ * + * @deprecated 使用 TuskClientService.getOnlineRobots() 代替 + * @return AGV状态列表(空列表) + */ + @Deprecated + List> queryWarehouseAgvStatus(); + + /** + * 查询立体仓库领料申请单统计 + * + *

数据说明:

+ *
    + *
  • total - 总数
  • + *
  • completed - 已完成数
  • + *
  • processing - 处理中数
  • + *
  • pending - 待处理数
  • + *
  • completionRate - 完成率百分比
  • + *
+ * + * @return 领料申请单统计 + */ + Map queryWarehouseMaterialRequestStats(); + + /** + * 查询立体仓库发货统计 + * + *

数据说明:

+ *
    + *
  • total - 总数
  • + *
  • completed - 已完成数
  • + *
  • processing - 处理中数
  • + *
  • pending - 待处理数
  • + *
  • completionRate - 完成率百分比
  • + *
+ * + * @return 发货统计 + */ + Map queryWarehouseShipmentStats(); } diff --git a/src/main/java/com/gaotao/modules/dashboard/service/DashboardWebSocketService.java b/src/main/java/com/gaotao/modules/dashboard/service/DashboardWebSocketService.java index 71e3472..e0b5917 100644 --- a/src/main/java/com/gaotao/modules/dashboard/service/DashboardWebSocketService.java +++ b/src/main/java/com/gaotao/modules/dashboard/service/DashboardWebSocketService.java @@ -24,6 +24,7 @@ import java.util.Map; *
  • /topic/dashboard/robot-picking - 机械臂拣选看板
  • *
  • /topic/dashboard/slitting-board - 分切区看板
  • *
  • /topic/dashboard/inventory-board - 库存分析看板
  • + *
  • /topic/dashboard/warehouse-3d - 智能立体仓库看板
  • * * * @author System @@ -188,6 +189,21 @@ public class DashboardWebSocketService { } } + /** + * 推送智能立体仓库看板数据 + * + * @param data 看板数据 + */ + public void pushWarehouse3dBoardData(Map data) { + log.debug("推送智能立体仓库看板数据"); + try { + messagingTemplate.convertAndSend("/topic/dashboard/warehouse-3d", + R.ok().put("data", data)); + } catch (Exception e) { + log.error("推送智能立体仓库看板数据失败: {}", e.getMessage(), e); + } + } + /** * 推送到指定主题 * diff --git a/src/main/java/com/gaotao/modules/dashboard/task/DashboardPushTask.java b/src/main/java/com/gaotao/modules/dashboard/task/DashboardPushTask.java index 723af8b..3297a86 100644 --- a/src/main/java/com/gaotao/modules/dashboard/task/DashboardPushTask.java +++ b/src/main/java/com/gaotao/modules/dashboard/task/DashboardPushTask.java @@ -1,8 +1,12 @@ package com.gaotao.modules.dashboard.task; +import com.beust.ah.A; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.gaotao.common.utils.HttpUtils; +import com.gaotao.modules.automatedWarehouse.entity.tusk.AgvStatus; +import com.gaotao.modules.automatedWarehouse.entity.tusk.TuskResponse; +import com.gaotao.modules.automatedWarehouse.service.TuskClientService; import com.gaotao.modules.dashboard.service.DashboardWebSocketService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -46,10 +50,13 @@ public class DashboardPushTask { * 上次推送的数据哈希值(用于检测数据变更) */ private Map lastDataHash = new HashMap<>(); - + @Autowired private com.gaotao.modules.dashboard.dao.DashboardDao dashboardDao; + @Autowired(required = false) + private TuskClientService tuskClientService; + /** * 每5秒检查机械臂拣选数据并推送 * @@ -348,5 +355,281 @@ public class DashboardPushTask { return (assistArmList == null || assistArmList.isEmpty()) && (inboundList == null || inboundList.isEmpty()); } + + /** + * 每5秒检查智能立体仓库看板数据并推送 + * + *

    数据来源:

    + *
      + *
    • 任务统计数据 - queryWarehouseTaskStats
    • + *
    • 库位利用率 - queryWarehouseStorageUtilization
    • + *
    • 机器人状态 - queryWarehouseRobotStatus
    • + *
    • AGV状态 - queryWarehouseAgvStatus
    • + *
    • 领料申请单统计 - queryWarehouseMaterialRequestStats
    • + *
    • 发货统计 - queryWarehouseShipmentStats
    • + *
    + */ + @Scheduled(fixedRate = 5000) + public void pushWarehouse3dBoardData() { + try { + // 从数据库获取立体仓库看板数据 + Map data = getWarehouse3dBoardDataFromDb(); + + // 如果返回null,转换为空数据 + if (data == null) { + data = createEmptyWarehouse3dData(); + } + + // 计算数据哈希值 + int currentHash = data.hashCode(); + int lastHash = lastDataHash.getOrDefault("warehouse-3d", 0); + + // 只在数据变更时推送 + if (currentHash != lastHash) { + boolean isEmpty = isWarehouse3dDataEmpty(data); + if (isEmpty) { + log.info("=== 智能立体仓库看板数据为空,推送空数据 ==="); + } else { + log.info("=== 检测到智能立体仓库看板数据变更,推送到前端 ==="); + } + webSocketService.pushWarehouse3dBoardData(data); + lastDataHash.put("warehouse-3d", currentHash); + } else { + log.debug("智能立体仓库看板数据无变化,跳过推送"); + } + + } catch (Exception e) { + log.error("推送智能立体仓库看板数据失败,推送空数据: {}", e.getMessage(), e); + // 异常时推送空数据,避免前端显示过期数据 + try { + Map emptyData = createEmptyWarehouse3dData(); + webSocketService.pushWarehouse3dBoardData(emptyData); + lastDataHash.put("warehouse-3d", emptyData.hashCode()); + } catch (Exception ex) { + log.error("推送空数据失败: {}", ex.getMessage()); + } + } + } + + /** + * 从数据库获取智能立体仓库看板数据 + * + * @return 智能立体仓库看板数据 + */ + private Map getWarehouse3dBoardDataFromDb() { + try { + log.debug("开始从数据库获取智能立体仓库看板数据"); + + // 查询任务统计数据 + //Map taskStats = dashboardDao.queryWarehouseTaskStats(); + Map taskStats = new HashMap<>(); + log.debug("任务统计数据: {}", taskStats); + + // 查询库位利用率数据 + //Map storageUtilization = dashboardDao.queryWarehouseStorageUtilization(); + Map storageUtilization = new HashMap<>(); + log.debug("库位利用率数据: {}", storageUtilization); + + // 查询机器人状态数据 + //List> robotStatus = dashboardDao.queryWarehouseRobotStatus(); + List> robotStatus = new ArrayList<>(); + log.debug("查询到机器人状态数据: {}条", robotStatus != null ? robotStatus.size() : 0); + + // 查询AGV状态数据(从TUSK系统获取) + List> agvStatus = getAgvStatusFromTusk(); + log.debug("查询到AGV状态数据: {}条", agvStatus != null ? agvStatus.size() : 0); + + // 查询领料申请单统计 + //Map materialRequestStats = dashboardDao.queryWarehouseMaterialRequestStats(); + Map materialRequestStats = new HashMap<>(); + log.debug("领料申请单统计: {}", materialRequestStats); + + // 查询发货统计 + //Map shipmentStats = dashboardDao.queryWarehouseShipmentStats(); + Map shipmentStats = new HashMap<>(); + log.debug("发货统计: {}", shipmentStats); + + // 构造返回数据 + Map resultData = new HashMap<>(); + resultData.put("taskData", taskStats != null ? taskStats : new HashMap<>()); + resultData.put("storageData", storageUtilization != null ? storageUtilization : new HashMap<>()); + resultData.put("robotData", robotStatus != null ? robotStatus : new ArrayList<>()); + resultData.put("agvData", agvStatus != null ? agvStatus : new ArrayList<>()); + resultData.put("materialRequestData", materialRequestStats != null ? materialRequestStats : new HashMap<>()); + resultData.put("shipmentData", shipmentStats != null ? shipmentStats : new HashMap<>()); + + log.debug("智能立体仓库看板数据组装完成"); + return resultData; + + } catch (Exception e) { + log.error("从数据库获取智能立体仓库看板数据失败: {}", e.getMessage(), e); + return null; + } + } + + /** + * 创建空的智能立体仓库数据 + * + * @return 空的智能立体仓库数据结构 + */ + private Map createEmptyWarehouse3dData() { + Map emptyData = new HashMap<>(); + emptyData.put("taskData", new HashMap<>()); + emptyData.put("storageData", new HashMap<>()); + emptyData.put("robotData", new ArrayList<>()); + emptyData.put("agvData", new ArrayList<>()); + emptyData.put("materialRequestData", new HashMap<>()); + emptyData.put("shipmentData", new HashMap<>()); + return emptyData; + } + + /** + * 判断智能立体仓库数据是否为空 + * + * @param data 待检查的数据 + * @return true=数据为空,false=数据不为空 + */ + private boolean isWarehouse3dDataEmpty(Map data) { + if (data == null || data.isEmpty()) { + return true; + } + + Map taskData = (Map) data.get("taskData"); + Map storageData = (Map) data.get("storageData"); + List robotData = (List) data.get("robotData"); + List agvData = (List) data.get("agvData"); + + return (taskData == null || taskData.isEmpty()) + && (storageData == null || storageData.isEmpty()) + && (robotData == null || robotData.isEmpty()) + && (agvData == null || agvData.isEmpty()); + } + + /** + * 从TUSK系统获取AGV状态数据 + * + *

    数据转换说明:

    + *
      + *
    • 从TUSK获取原始AGV状态
    • + *
    • 转换为看板需要的格式
    • + *
    • 映射状态码为状态文本
    • + *
    + * + * @return AGV状态列表 + */ + private List> getAgvStatusFromTusk() { + List> agvList = new ArrayList<>(); + + try { + // 如果TUSK客户端服务未配置,返回空列表 + if (tuskClientService == null) { + log.debug("TUSK客户端服务未配置,跳过AGV状态查询"); + return agvList; + } + + // 调用TUSK接口获取在线AGV列表 + TuskResponse> response = tuskClientService.getOnlineRobots(); + + if (!response.isSuccess() || response.getData() == null) { + log.warn("从TUSK获取AGV状态失败: {}", response.getMsg()); + return agvList; + } + + // 转换TUSK数据为看板格式 + List tuskAgvList = response.getData(); + for (AgvStatus agvStatus : tuskAgvList) { + Map agv = new HashMap<>(); + + // AGV编号 + agv.put("id", agvStatus.getId()); + agv.put("name", "AGV#" + agvStatus.getId()); + + // 状态转换 + String status = convertAgvStatus(agvStatus.getAgvStat()); + agv.put("status", status.toLowerCase()); // working/idle/charging/error + agv.put("statusText", getAgvStatusText(agvStatus.getAgvStat())); + + // 电量 + agv.put("battery", agvStatus.getSoc()); + + // 当前任务数(根据状态判断:运行中为1,否则为0) + int tasks = (agvStatus.getAgvStat() >= 1 && agvStatus.getAgvStat() <= 12) ? 1 : 0; + agv.put("tasks", tasks); + + agvList.add(agv); + } + + log.debug("成功从TUSK获取{}个AGV状态", agvList.size()); + + } catch (Exception e) { + log.error("从TUSK获取AGV状态异常: {}", e.getMessage(), e); + } + + return agvList; + } + + /** + * 转换AGV状态码为标准状态 + * + * @param agvStat TUSK系统的AGV状态码 + * @return 标准状态 (working/idle/charging/error) + */ + private String convertAgvStatus(Integer agvStat) { + if (agvStat == null) { + return "idle"; + } + + if (agvStat == 0) { + return "idle"; // 空闲 + } else if (agvStat >= 1 && agvStat <= 12) { + return "working"; // 运行中 + } else if (agvStat == 13) { + return "charging"; // 充电中 + } else if (agvStat >= 128) { + return "error"; // 异常状态 + } + + return "idle"; + } + + /** + * 获取AGV状态文本(中文) + * + * @param agvStat TUSK系统的AGV状态码 + * @return 状态文本 + */ + private String getAgvStatusText(Integer agvStat) { + if (agvStat == null) { + return "空闲"; + } + + if (agvStat == 0) { + return "空闲"; + } else if (agvStat == 1) { + return "运行中"; + } else if (agvStat == 2) { + return "直线运动中"; + } else if (agvStat == 3) { + return "旋转中"; + } else if (agvStat == 13) { + return "充电中"; + } else if (agvStat == 23) { + return "暂停"; + } else if (agvStat == 128) { + return "异常状态"; + } else if (agvStat == 129) { + return "急停"; + } else if (agvStat == 130) { + return "碰撞告警"; + } else if (agvStat == 131) { + return "告警"; + } else if (agvStat >= 1 && agvStat <= 12) { + return "运行中"; + } else if (agvStat >= 128) { + return "异常"; + } + + return "未知状态"; + } } diff --git a/src/main/java/com/gaotao/modules/po/service/impl/PoServiceImpl.java b/src/main/java/com/gaotao/modules/po/service/impl/PoServiceImpl.java index 89f56c4..e362173 100644 --- a/src/main/java/com/gaotao/modules/po/service/impl/PoServiceImpl.java +++ b/src/main/java/com/gaotao/modules/po/service/impl/PoServiceImpl.java @@ -172,8 +172,14 @@ public class PoServiceImpl extends ServiceImpl implemen if (partData!=null && !partData.isEmpty()) { shelfLife = partData.getFirst().get("durabilityDays")!=null?(Integer) partData.getFirst().get("durabilityDays"):null; } + // 根据库位获取仓库ID + String warehouseId = null; + Location location = locationService.getByLocationIdAndSite(inData.getSite(), inData.getLocationId()); + if (location != null) { + warehouseId = location.getWarehouseId(); + } // 创建采购接收记录 - 在库存更新之前创建 - PoReceiptDetail receiptDetail = createPoReceiptRecords(inData,shelfLife); + PoReceiptDetail receiptDetail = createPoReceiptRecords(inData,shelfLife,warehouseId); String receiptNo = receiptDetail.getReceiptNo(); SysUserEntity currentUser = (SysUserEntity) SecurityUtils.getSubject().getPrincipal(); String transType = "CRT"; @@ -184,7 +190,7 @@ public class PoServiceImpl extends ServiceImpl implemen transHeader.setTransNo(transNo.getNewTransNo()); transHeader.setTransDate(new Date()); transHeader.setTransTypeDb(transType); - transHeader.setWarehouseId(inData.getWarehouseId()); + transHeader.setWarehouseId(warehouseId); transHeader.setUserId(currentUser.getUserId().toString()); transHeader.setUserName(currentUser.getUserDisplay()); transHeader.setRemark(inData.getRemark()); @@ -522,7 +528,7 @@ public class PoServiceImpl extends ServiceImpl implemen /** * 创建采购接收记录 */ - private PoReceiptDetail createPoReceiptRecords(TransDetailDto inData,Integer shelfLife) { + private PoReceiptDetail createPoReceiptRecords(TransDetailDto inData,Integer shelfLife,String warehouseId) { // 生成接收单号 TransNoControl receiptNoControl = transNoService.getTransNo(inData.getSite(), "PR", 10); String receiptNo = receiptNoControl.getNewTransNo(); @@ -542,7 +548,7 @@ public class PoServiceImpl extends ServiceImpl implemen //poReceipt.setDeliveryNoteNo(inData.getPoNo()); // UI和ifs没有返回送货单号 poReceipt.setPrinted("N"); poReceipt.setRemark("PO接收自动创建 - " + inData.getRemark()); - poReceipt.setWarehouseId(inData.getWarehouseId()); + poReceipt.setWarehouseId(warehouseId); poReceipt.setOutWorkOrderFlag("N"); poReceipt.setEmailCanSendFlag("N"); poReceipt.setOrderref1(inData.getPoNo()); diff --git a/src/main/resources/mapper/dashboard/DashboardDao.xml b/src/main/resources/mapper/dashboard/DashboardDao.xml index 1870452..3f3acb2 100644 --- a/src/main/resources/mapper/dashboard/DashboardDao.xml +++ b/src/main/resources/mapper/dashboard/DashboardDao.xml @@ -27,5 +27,243 @@ ORDER BY storage_location ASC + + + + + + + + + + + + + + + + + + + + +