10 changed files with 1432 additions and 29 deletions
-
6build.gradle
-
21src/main/java/com/gaotao/common/utils/AgvClientUtil.java
-
1src/main/java/com/gaotao/config/ShiroConfig.java
-
68src/main/java/com/gaotao/config/WebSocketConfig.java
-
138src/main/java/com/gaotao/modules/dashboard/dao/DashboardDao.java
-
222src/main/java/com/gaotao/modules/dashboard/service/DashboardWebSocketService.java
-
635src/main/java/com/gaotao/modules/dashboard/task/DashboardPushTask.java
-
87src/main/java/com/gaotao/modules/handlingunit/controller/PdaLabelController.java
-
14src/main/java/com/gaotao/modules/po/service/impl/PoServiceImpl.java
-
269src/main/resources/mapper/dashboard/DashboardDao.xml
@ -0,0 +1,68 @@ |
|||
package com.gaotao.config; |
|||
|
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.springframework.messaging.simp.config.MessageBrokerRegistry; |
|||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; |
|||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry; |
|||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; |
|||
|
|||
/** |
|||
* WebSocket 配置类 |
|||
* |
|||
* <p><b>功能说明:</b></p> |
|||
* <ul> |
|||
* <li>启用STOMP协议的WebSocket消息代理</li> |
|||
* <li>配置消息端点和订阅前缀</li> |
|||
* <li>支持跨域访问</li> |
|||
* <li>提供SockJS降级支持</li> |
|||
* </ul> |
|||
* |
|||
* <p><b>订阅主题说明:</b></p> |
|||
* <ul> |
|||
* <li>/topic/dashboard/manual-picking - 人工拣选看板</li> |
|||
* <li>/topic/dashboard/robot-picking - 机械臂拣选看板</li> |
|||
* <li>/topic/dashboard/slitting-board - 分切区看板</li> |
|||
* <li>/topic/dashboard/inventory-board - 库存分析看板</li> |
|||
* </ul> |
|||
* |
|||
* @author System |
|||
* @since 2025-01-23 |
|||
*/ |
|||
@Configuration |
|||
@EnableWebSocketMessageBroker |
|||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { |
|||
|
|||
/** |
|||
* 配置消息代理 |
|||
* |
|||
* @param config 消息代理注册器 |
|||
*/ |
|||
@Override |
|||
public void configureMessageBroker(MessageBrokerRegistry config) { |
|||
// 启用简单消息代理,用于向客户端推送消息 |
|||
// /topic - 公共广播(一对多) |
|||
// /queue - 点对点(一对一) |
|||
config.enableSimpleBroker("/topic", "/queue"); |
|||
|
|||
// 客户端发送消息的目的地前缀 |
|||
config.setApplicationDestinationPrefixes("/app"); |
|||
|
|||
// 点对点消息的用户前缀 |
|||
config.setUserDestinationPrefix("/user"); |
|||
} |
|||
|
|||
/** |
|||
* 注册STOMP端点 |
|||
* |
|||
* @param registry STOMP端点注册器 |
|||
*/ |
|||
@Override |
|||
public void registerStompEndpoints(StompEndpointRegistry registry) { |
|||
// 注册WebSocket端点:/ws/dashboard |
|||
// 前端通过此端点建立WebSocket连接 |
|||
registry.addEndpoint("/ws/dashboard") |
|||
.setAllowedOriginPatterns("*") // 允许所有跨域访问 |
|||
.withSockJS(); // 启用SockJS降级支持(当浏览器不支持WebSocket时) |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,138 @@ |
|||
package com.gaotao.modules.dashboard.dao; |
|||
|
|||
import org.apache.ibatis.annotations.Mapper; |
|||
|
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* 看板数据访问接口 |
|||
* |
|||
* <p><b>功能说明:</b>从数据库视图获取看板实时数据</p> |
|||
* |
|||
* <p><b>数据来源:</b></p> |
|||
* <ul> |
|||
* <li>view_board_slitting_assist_arm - 分切区助力臂数据</li> |
|||
* <li>view_board_slitting_inbound - 分切区入库数据</li> |
|||
* <li>智能立体仓库相关视图/表 - 任务统计、库位、设备状态等</li> |
|||
* </ul> |
|||
* |
|||
* @author System |
|||
* @since 2025-01-23 |
|||
*/ |
|||
@Mapper |
|||
public interface DashboardDao { |
|||
|
|||
/** |
|||
* 查询分切区助力臂数据 |
|||
* |
|||
* @return 助力臂区实时数据 |
|||
*/ |
|||
List<Map<String, Object>> querySlittingAssistArmData(); |
|||
|
|||
/** |
|||
* 查询分切区入库数据 |
|||
* |
|||
* @return 分切入库区实时数据 |
|||
*/ |
|||
List<Map<String, Object>> querySlittingInboundData(); |
|||
|
|||
// ==================== 智能立体仓库看板数据 ==================== |
|||
|
|||
/** |
|||
* 查询立体仓库任务统计数据 |
|||
* |
|||
* <p><b>数据说明:</b></p> |
|||
* <ul> |
|||
* <li>totalTasks - 累计任务总数</li> |
|||
* <li>monthlyTasks - 月度作业总数</li> |
|||
* <li>outboundTasks - 出库作业数</li> |
|||
* <li>inboundTasks - 入库作业数</li> |
|||
* <li>outboundPercent - 出库占比</li> |
|||
* <li>inboundPercent - 入库占比</li> |
|||
* </ul> |
|||
* |
|||
* @return 任务统计数据 |
|||
*/ |
|||
Map<String, Object> queryWarehouseTaskStats(); |
|||
|
|||
/** |
|||
* 查询立体仓库库位利用率数据 |
|||
* |
|||
* <p><b>数据说明:</b></p> |
|||
* <ul> |
|||
* <li>totalSlots - 总库位数</li> |
|||
* <li>usedSlots - 已使用库位数</li> |
|||
* <li>utilizationRate - 利用率百分比</li> |
|||
* <li>steelPallet - 钢制托盘数量</li> |
|||
* <li>guardPallet - 护边托盘数量</li> |
|||
* <li>flatPallet - 平托盘数量</li> |
|||
* </ul> |
|||
* |
|||
* @return 库位利用率数据 |
|||
*/ |
|||
Map<String, Object> queryWarehouseStorageUtilization(); |
|||
|
|||
/** |
|||
* 查询立体仓库机器人状态数据 |
|||
* |
|||
* <p><b>数据说明:</b></p> |
|||
* <ul> |
|||
* <li>id - 机器人ID</li> |
|||
* <li>name - 机器人名称</li> |
|||
* <li>status - 状态(working/idle/charging/error)</li> |
|||
* <li>statusText - 状态文本</li> |
|||
* <li>efficiency - 效率百分比</li> |
|||
* <li>tasks - 当前任务数</li> |
|||
* </ul> |
|||
* |
|||
* @return 机器人状态列表 |
|||
*/ |
|||
List<Map<String, Object>> queryWarehouseRobotStatus(); |
|||
|
|||
/** |
|||
* 查询立体仓库AGV状态数据 |
|||
* |
|||
* <p><b>注意:此方法已废弃!</b></p> |
|||
* <p>AGV状态数据不从数据库查询,而是从TUSK系统实时获取。</p> |
|||
* <p>请使用 DashboardPushTask.getAgvStatusFromTusk() 方法获取AGV状态。</p> |
|||
* |
|||
* @deprecated 使用 TuskClientService.getOnlineRobots() 代替 |
|||
* @return AGV状态列表(空列表) |
|||
*/ |
|||
@Deprecated |
|||
List<Map<String, Object>> queryWarehouseAgvStatus(); |
|||
|
|||
/** |
|||
* 查询立体仓库领料申请单统计 |
|||
* |
|||
* <p><b>数据说明:</b></p> |
|||
* <ul> |
|||
* <li>total - 总数</li> |
|||
* <li>completed - 已完成数</li> |
|||
* <li>processing - 处理中数</li> |
|||
* <li>pending - 待处理数</li> |
|||
* <li>completionRate - 完成率百分比</li> |
|||
* </ul> |
|||
* |
|||
* @return 领料申请单统计 |
|||
*/ |
|||
Map<String, Object> queryWarehouseMaterialRequestStats(); |
|||
|
|||
/** |
|||
* 查询立体仓库发货统计 |
|||
* |
|||
* <p><b>数据说明:</b></p> |
|||
* <ul> |
|||
* <li>total - 总数</li> |
|||
* <li>completed - 已完成数</li> |
|||
* <li>processing - 处理中数</li> |
|||
* <li>pending - 待处理数</li> |
|||
* <li>completionRate - 完成率百分比</li> |
|||
* </ul> |
|||
* |
|||
* @return 发货统计 |
|||
*/ |
|||
Map<String, Object> queryWarehouseShipmentStats(); |
|||
} |
|||
|
|||
@ -0,0 +1,222 @@ |
|||
package com.gaotao.modules.dashboard.service; |
|||
|
|||
import com.gaotao.common.utils.R; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.messaging.simp.SimpMessagingTemplate; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* 看板WebSocket推送服务 |
|||
* |
|||
* <p><b>功能说明:</b></p> |
|||
* <ul> |
|||
* <li>向订阅的前端看板推送实时数据</li> |
|||
* <li>支持按看板类型定向推送</li> |
|||
* <li>支持广播推送到所有看板</li> |
|||
* </ul> |
|||
* |
|||
* <p><b>订阅主题列表:</b></p> |
|||
* <ul> |
|||
* <li>/topic/dashboard/manual-picking - 人工拣选看板</li> |
|||
* <li>/topic/dashboard/robot-picking - 机械臂拣选看板</li> |
|||
* <li>/topic/dashboard/slitting-board - 分切区看板</li> |
|||
* <li>/topic/dashboard/inventory-board - 库存分析看板</li> |
|||
* <li>/topic/dashboard/warehouse-3d - 智能立体仓库看板</li> |
|||
* </ul> |
|||
* |
|||
* @author System |
|||
* @since 2025-01-23 |
|||
*/ |
|||
@Slf4j |
|||
@Service |
|||
public class DashboardWebSocketService { |
|||
|
|||
@Autowired |
|||
private SimpMessagingTemplate messagingTemplate; |
|||
|
|||
/** |
|||
* 推送人工拣选看板数据 |
|||
* |
|||
* @param data 看板数据 |
|||
*/ |
|||
public void pushManualPickingData(Map<String, Object> data) { |
|||
log.debug("推送人工拣选看板数据"); |
|||
try { |
|||
messagingTemplate.convertAndSend("/topic/dashboard/manual-picking", |
|||
R.ok().put("data", data)); |
|||
} catch (Exception e) { |
|||
log.error("推送人工拣选看板数据失败: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 推送机械臂拣选看板数据 |
|||
* |
|||
* @param data 看板数据 |
|||
*/ |
|||
public void pushRobotPickingData(Map<String, Object> data) { |
|||
log.debug("推送机械臂拣选看板数据: 周转箱{}条, 原材{}条", |
|||
data.get("containerList") != null ? ((java.util.List<?>)data.get("containerList")).size() : 0, |
|||
data.get("materialList") != null ? ((java.util.List<?>)data.get("materialList")).size() : 0); |
|||
try { |
|||
messagingTemplate.convertAndSend("/topic/dashboard/robot-picking", |
|||
R.ok().put("data", data)); |
|||
} catch (Exception e) { |
|||
log.error("推送机械臂拣选看板数据失败: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 推送分切区看板数据 |
|||
* |
|||
* @param data 看板数据 |
|||
*/ |
|||
public void pushSlittingBoardData(Map<String, Object> data) { |
|||
log.debug("推送分切区看板数据"); |
|||
try { |
|||
messagingTemplate.convertAndSend("/topic/dashboard/slitting-board", |
|||
R.ok().put("data", data)); |
|||
} catch (Exception e) { |
|||
log.error("推送分切区看板数据失败: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 推送库存分析看板数据 |
|||
* |
|||
* @param data 看板数据 |
|||
*/ |
|||
public void pushInventoryBoardData(Map<String, Object> data) { |
|||
log.debug("推送库存分析看板数据"); |
|||
try { |
|||
messagingTemplate.convertAndSend("/topic/dashboard/inventory-board", |
|||
R.ok().put("data", data)); |
|||
} catch (Exception e) { |
|||
log.error("推送库存分析看板数据失败: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 推送成品入库出库区看板数据 |
|||
* |
|||
* @param data 看板数据 |
|||
*/ |
|||
public void pushFinishedProductBoardData(Map<String, Object> data) { |
|||
log.debug("推送成品入库出库区看板数据"); |
|||
try { |
|||
messagingTemplate.convertAndSend("/topic/dashboard/finished-product-board", |
|||
R.ok().put("data", data)); |
|||
} catch (Exception e) { |
|||
log.error("推送成品入库出库区看板数据失败: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 推送原材收货区看板数据 |
|||
* |
|||
* @param data 看板数据 |
|||
*/ |
|||
public void pushMaterialReceivingBoardData(Map<String, Object> data) { |
|||
log.debug("推送原材收货区看板数据"); |
|||
try { |
|||
messagingTemplate.convertAndSend("/topic/dashboard/material-receiving-board", |
|||
R.ok().put("data", data)); |
|||
} catch (Exception e) { |
|||
log.error("推送原材收货区看板数据失败: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 推送缓存区看板数据 |
|||
* |
|||
* @param data 看板数据 |
|||
*/ |
|||
public void pushBufferBoardData(Map<String, Object> data) { |
|||
log.debug("推送缓存区看板数据"); |
|||
try { |
|||
messagingTemplate.convertAndSend("/topic/dashboard/buffer-board", |
|||
R.ok().put("data", data)); |
|||
} catch (Exception e) { |
|||
log.error("推送缓存区看板数据失败: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 推送车间AGV放料区看板数据 |
|||
* |
|||
* @param data 看板数据 |
|||
*/ |
|||
public void pushWorkshopFeedingBoardData(Map<String, Object> data) { |
|||
log.debug("推送车间AGV放料区看板数据"); |
|||
try { |
|||
messagingTemplate.convertAndSend("/topic/dashboard/workshop-feeding-board", |
|||
R.ok().put("data", data)); |
|||
} catch (Exception e) { |
|||
log.error("推送车间AGV放料区看板数据失败: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 推送异常处理区看板数据 |
|||
* |
|||
* @param data 看板数据 |
|||
*/ |
|||
public void pushExceptionBoardData(Map<String, Object> data) { |
|||
log.debug("推送异常处理区看板数据"); |
|||
try { |
|||
messagingTemplate.convertAndSend("/topic/dashboard/exception-board", |
|||
R.ok().put("data", data)); |
|||
} catch (Exception e) { |
|||
log.error("推送异常处理区看板数据失败: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 广播推送到所有看板 |
|||
* |
|||
* @param data 看板数据 |
|||
*/ |
|||
public void broadcastToAllDashboards(Map<String, Object> data) { |
|||
log.info("广播推送到所有看板"); |
|||
try { |
|||
messagingTemplate.convertAndSend("/topic/dashboard/broadcast", |
|||
R.ok().put("data", data)); |
|||
} catch (Exception e) { |
|||
log.error("广播推送到所有看板失败: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 推送智能立体仓库看板数据 |
|||
* |
|||
* @param data 看板数据 |
|||
*/ |
|||
public void pushWarehouse3dBoardData(Map<String, Object> data) { |
|||
log.debug("推送智能立体仓库看板数据"); |
|||
try { |
|||
messagingTemplate.convertAndSend("/topic/dashboard/warehouse-3d", |
|||
R.ok().put("data", data)); |
|||
} catch (Exception e) { |
|||
log.error("推送智能立体仓库看板数据失败: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 推送到指定主题 |
|||
* |
|||
* @param topic 主题名称 |
|||
* @param data 数据 |
|||
*/ |
|||
public void pushToTopic(String topic, Object data) { |
|||
log.debug("推送数据到主题: {}", topic); |
|||
try { |
|||
messagingTemplate.convertAndSend(topic, R.ok().put("data", data)); |
|||
} catch (Exception e) { |
|||
log.error("推送数据到主题{}失败: {}", topic, e.getMessage(), e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,635 @@ |
|||
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; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.util.*; |
|||
|
|||
/** |
|||
* 看板数据推送定时任务 |
|||
* |
|||
* <p><b>功能说明:</b></p> |
|||
* <ul> |
|||
* <li>定时从WCS Board API获取最新数据</li> |
|||
* <li>检测到数据变更后通过WebSocket推送到前端</li> |
|||
* <li>相比轮询,只在数据变更时推送,减少无效传输</li> |
|||
* </ul> |
|||
* |
|||
* <p><b>推送策略:</b></p> |
|||
* <ul> |
|||
* <li>每5秒检查一次机械臂拣选数据</li> |
|||
* <li>数据有变化时立即推送</li> |
|||
* <li>推送失败时记录日志,不影响下次推送</li> |
|||
* </ul> |
|||
* |
|||
* @author System |
|||
* @since 2025-01-23 |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
public class DashboardPushTask { |
|||
|
|||
@Autowired |
|||
private DashboardWebSocketService webSocketService; |
|||
|
|||
@Value("${custom.wcs-board-api}") |
|||
private String wcsBoardApi; |
|||
|
|||
/** |
|||
* 上次推送的数据哈希值(用于检测数据变更) |
|||
*/ |
|||
private Map<String, Integer> lastDataHash = new HashMap<>(); |
|||
|
|||
@Autowired |
|||
private com.gaotao.modules.dashboard.dao.DashboardDao dashboardDao; |
|||
|
|||
@Autowired(required = false) |
|||
private TuskClientService tuskClientService; |
|||
|
|||
/** |
|||
* 每5秒检查机械臂拣选数据并推送 |
|||
* |
|||
* <p>注意:这个间隔可以根据实际需求调整</p> |
|||
* <ul> |
|||
* <li>如果数据变化频繁,可以缩短间隔(如2-3秒)</li> |
|||
* <li>如果数据变化不频繁,可以延长间隔(如10-15秒)</li> |
|||
* </ul> |
|||
*/ |
|||
@Scheduled(fixedRate = 5000) |
|||
public void pushRobotPickingData() { |
|||
try { |
|||
// 从WCS Board API获取机械臂拣选数据 |
|||
Map<String, Object> data = getRobotPickingDataFromWcs(); |
|||
|
|||
// 如果返回null,转换为空数据(避免前端显示过期数据) |
|||
if (data == null) { |
|||
data = createEmptyData(); |
|||
} |
|||
|
|||
// 计算数据哈希值 |
|||
int currentHash = data.hashCode(); |
|||
int lastHash = lastDataHash.getOrDefault("robot-picking", 0); |
|||
|
|||
// 只在数据变更时推送(包括从有数据变为空数据) |
|||
if (currentHash != lastHash) { |
|||
boolean isEmpty = isDataEmpty(data); |
|||
if (isEmpty) { |
|||
log.info("=== 机械臂拣选数据为空,推送空数据清空前端列表 ==="); |
|||
} else { |
|||
int containerCount = ((List<?>) data.get("containerList")).size(); |
|||
int materialCount = ((List<?>) data.get("materialList")).size(); |
|||
log.info("=== 检测到机械臂拣选数据变更,推送到前端(周转箱:{}条,原材:{}条)===", |
|||
containerCount, materialCount); |
|||
} |
|||
webSocketService.pushRobotPickingData(data); |
|||
lastDataHash.put("robot-picking", currentHash); |
|||
} else { |
|||
log.debug("机械臂拣选数据无变化,跳过推送"); |
|||
} |
|||
|
|||
} catch (Exception e) { |
|||
log.error("推送机械臂拣选数据失败,推送空数据清空前端列表: {}", e.getMessage(), e); |
|||
// 异常时推送空数据,避免前端显示过期数据 |
|||
try { |
|||
Map<String, Object> emptyData = createEmptyData(); |
|||
webSocketService.pushRobotPickingData(emptyData); |
|||
lastDataHash.put("robot-picking", emptyData.hashCode()); |
|||
} catch (Exception ex) { |
|||
log.error("推送空数据失败: {}", ex.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 从WCS Board API获取机械臂拣选数据 |
|||
* |
|||
* @return 机械臂拣选数据 |
|||
*/ |
|||
private Map<String, Object> getRobotPickingDataFromWcs() { |
|||
try { |
|||
// 调用WCS Board API |
|||
String url = wcsBoardApi + "WmsDashboard/auto-sorting-info"; |
|||
String wcsResponse = HttpUtils.doGet(url, null, null); |
|||
|
|||
// 解析JSON数据 |
|||
ObjectMapper mapper = new ObjectMapper(); |
|||
JsonNode rootNode = mapper.readTree(wcsResponse); |
|||
|
|||
// 检查返回码 |
|||
int resCode = rootNode.get("resCode").asInt(); |
|||
if (resCode != 200) { |
|||
log.warn("WCS API返回错误: code={}", resCode); |
|||
return null; |
|||
} |
|||
|
|||
// 获取sortingStations数组 |
|||
JsonNode resData = rootNode.get("resData"); |
|||
if (resData == null || !resData.has("sortingStations")) { |
|||
log.warn("WCS返回数据中没有sortingStations"); |
|||
return createEmptyData(); |
|||
} |
|||
|
|||
JsonNode sortingStations = resData.get("sortingStations"); |
|||
|
|||
// 按照sortingStation分类处理数据 |
|||
List<Map<String, Object>> containerList = new ArrayList<>(); // 1071-周转箱 |
|||
List<Map<String, Object>> materialList = new ArrayList<>(); // 1060-原材 |
|||
|
|||
for (JsonNode station : sortingStations) { |
|||
String sortingStation = station.get("sortingStation").asText(); |
|||
JsonNode materials = station.get("materials"); |
|||
|
|||
if (materials != null && materials.isArray()) { |
|||
for (JsonNode material : materials) { |
|||
Map<String, Object> item = convertMaterialToItem(material, sortingStation); |
|||
|
|||
// 根据工作站分类 |
|||
if ("1071".equals(sortingStation)) { |
|||
containerList.add(item); |
|||
} else if ("1060".equals(sortingStation)) { |
|||
materialList.add(item); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 构造返回数据 |
|||
Map<String, Object> resultData = new HashMap<>(); |
|||
resultData.put("containerList", containerList); |
|||
resultData.put("materialList", materialList); |
|||
|
|||
return resultData; |
|||
|
|||
} catch (Exception e) { |
|||
log.error("从WCS获取机械臂拣选数据失败: {}", e.getMessage()); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 将WCS返回的material数据转换为前端需要的格式 |
|||
* |
|||
* @param material WCS返回的物料数据 |
|||
* @param sortingStation 工作站编号 |
|||
* @return 前端表格数据格式 |
|||
*/ |
|||
private Map<String, Object> convertMaterialToItem(JsonNode material, String sortingStation) { |
|||
Map<String, Object> item = new HashMap<>(); |
|||
|
|||
// 拣选托盘码 (源托盘) |
|||
item.put("pickingBatchNo", material.has("sourcePalletCode") ? |
|||
material.get("sourcePalletCode").asText() : ""); |
|||
|
|||
// 拣选物料名称 (SKU) |
|||
item.put("pickingMaterialName", material.has("sku") ? |
|||
material.get("sku").asText() : ""); |
|||
|
|||
// RFID条码 |
|||
item.put("rfidBarcode", material.has("rfidBarcode") ? |
|||
material.get("rfidBarcode").asText() : ""); |
|||
|
|||
// 状态 (根据isCompleted判断) |
|||
boolean isCompleted = material.has("isCompleted") && material.get("isCompleted").asBoolean(); |
|||
item.put("status", isCompleted ? "完成" : "等待分拣"); |
|||
|
|||
// 存放托盘码 (目标托盘) |
|||
item.put("storageBatchNo", material.has("targetPalletCode") ? |
|||
material.get("targetPalletCode").asText() : ""); |
|||
|
|||
// 存放位置 (工作站编号) |
|||
item.put("storageLocation", material.has("sortingStation") ? |
|||
material.get("sortingStation").asText() : sortingStation); |
|||
|
|||
return item; |
|||
} |
|||
|
|||
/** |
|||
* 创建空数据 |
|||
* |
|||
* @return 空的数据结构 |
|||
*/ |
|||
private Map<String, Object> createEmptyData() { |
|||
Map<String, Object> emptyData = new HashMap<>(); |
|||
emptyData.put("containerList", new ArrayList<>()); |
|||
emptyData.put("materialList", new ArrayList<>()); |
|||
return emptyData; |
|||
} |
|||
|
|||
/** |
|||
* 判断数据是否为空 |
|||
* |
|||
* @param data 待检查的数据 |
|||
* @return true=数据为空,false=数据不为空 |
|||
*/ |
|||
private boolean isDataEmpty(Map<String, Object> data) { |
|||
if (data == null || data.isEmpty()) { |
|||
return true; |
|||
} |
|||
|
|||
List<?> containerList = (List<?>) data.get("containerList"); |
|||
List<?> materialList = (List<?>) data.get("materialList"); |
|||
|
|||
return (containerList == null || containerList.isEmpty()) |
|||
&& (materialList == null || materialList.isEmpty()); |
|||
} |
|||
|
|||
/** |
|||
* 每5秒检查分切区看板数据并推送 |
|||
* |
|||
* <p><b>数据来源:</b></p> |
|||
* <ul> |
|||
* <li>view_board_slitting_assist_arm - 助力臂区数据</li> |
|||
* <li>view_board_slitting_inbound - 分切入库区数据</li> |
|||
* </ul> |
|||
*/ |
|||
@Scheduled(fixedRate = 5000) |
|||
public void pushSlittingBoardData() { |
|||
try { |
|||
// 从数据库视图获取分切区数据 |
|||
Map<String, Object> data = getSlittingBoardDataFromDb(); |
|||
|
|||
// 如果返回null,转换为空数据 |
|||
if (data == null) { |
|||
data = createEmptySlittingData(); |
|||
} |
|||
|
|||
// 计算数据哈希值 |
|||
int currentHash = data.hashCode(); |
|||
int lastHash = lastDataHash.getOrDefault("slitting-board", 0); |
|||
|
|||
// 只在数据变更时推送 |
|||
if (currentHash != lastHash) { |
|||
boolean isEmpty = isSlittingDataEmpty(data); |
|||
if (isEmpty) { |
|||
log.info("=== 分切区看板数据为空,推送空数据清空前端列表 ==="); |
|||
} else { |
|||
int assistArmCount = ((List<?>) data.get("assistArmList")).size(); |
|||
int inboundCount = ((List<?>) data.get("slittingInboundList")).size(); |
|||
log.info("=== 检测到分切区看板数据变更,推送到前端(助力臂:{}条,入库:{}条)===", |
|||
assistArmCount, inboundCount); |
|||
} |
|||
webSocketService.pushSlittingBoardData(data); |
|||
lastDataHash.put("slitting-board", currentHash); |
|||
} else { |
|||
log.debug("分切区看板数据无变化,跳过推送"); |
|||
} |
|||
|
|||
} catch (Exception e) { |
|||
log.error("推送分切区看板数据失败,推送空数据清空前端列表: {}", e.getMessage(), e); |
|||
// 异常时推送空数据,避免前端显示过期数据 |
|||
try { |
|||
Map<String, Object> emptyData = createEmptySlittingData(); |
|||
webSocketService.pushSlittingBoardData(emptyData); |
|||
lastDataHash.put("slitting-board", emptyData.hashCode()); |
|||
} catch (Exception ex) { |
|||
log.error("推送空数据失败: {}", ex.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 从数据库视图获取分切区看板数据 |
|||
* |
|||
* @return 分切区看板数据 |
|||
*/ |
|||
private Map<String, Object> getSlittingBoardDataFromDb() { |
|||
try { |
|||
// 查询助力臂区数据 |
|||
List<Map<String, Object>> assistArmList = dashboardDao.querySlittingAssistArmData(); |
|||
log.debug("查询到助力臂区数据: {}条", assistArmList != null ? assistArmList.size() : 0); |
|||
|
|||
// 查询分切入库区数据 |
|||
List<Map<String, Object>> inboundList = dashboardDao.querySlittingInboundData(); |
|||
log.debug("查询到分切入库区数据: {}条", inboundList != null ? inboundList.size() : 0); |
|||
|
|||
// 构造返回数据 |
|||
Map<String, Object> resultData = new HashMap<>(); |
|||
resultData.put("assistArmList", assistArmList != null ? assistArmList : new ArrayList<>()); |
|||
resultData.put("slittingInboundList", inboundList != null ? inboundList : new ArrayList<>()); |
|||
|
|||
return resultData; |
|||
|
|||
} catch (Exception e) { |
|||
log.error("从数据库获取分切区看板数据失败: {}", e.getMessage(), e); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 创建空的分切区数据 |
|||
* |
|||
* @return 空的分切区数据结构 |
|||
*/ |
|||
private Map<String, Object> createEmptySlittingData() { |
|||
Map<String, Object> emptyData = new HashMap<>(); |
|||
emptyData.put("assistArmList", new ArrayList<>()); |
|||
emptyData.put("slittingInboundList", new ArrayList<>()); |
|||
return emptyData; |
|||
} |
|||
|
|||
/** |
|||
* 判断分切区数据是否为空 |
|||
* |
|||
* @param data 待检查的数据 |
|||
* @return true=数据为空,false=数据不为空 |
|||
*/ |
|||
private boolean isSlittingDataEmpty(Map<String, Object> data) { |
|||
if (data == null || data.isEmpty()) { |
|||
return true; |
|||
} |
|||
|
|||
List<?> assistArmList = (List<?>) data.get("assistArmList"); |
|||
List<?> inboundList = (List<?>) data.get("slittingInboundList"); |
|||
|
|||
return (assistArmList == null || assistArmList.isEmpty()) |
|||
&& (inboundList == null || inboundList.isEmpty()); |
|||
} |
|||
|
|||
/** |
|||
* 每5秒检查智能立体仓库看板数据并推送 |
|||
* |
|||
* <p><b>数据来源:</b></p> |
|||
* <ul> |
|||
* <li>任务统计数据 - queryWarehouseTaskStats</li> |
|||
* <li>库位利用率 - queryWarehouseStorageUtilization</li> |
|||
* <li>机器人状态 - queryWarehouseRobotStatus</li> |
|||
* <li>AGV状态 - queryWarehouseAgvStatus</li> |
|||
* <li>领料申请单统计 - queryWarehouseMaterialRequestStats</li> |
|||
* <li>发货统计 - queryWarehouseShipmentStats</li> |
|||
* </ul> |
|||
*/ |
|||
@Scheduled(fixedRate = 5000) |
|||
public void pushWarehouse3dBoardData() { |
|||
try { |
|||
// 从数据库获取立体仓库看板数据 |
|||
Map<String, Object> 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<String, Object> emptyData = createEmptyWarehouse3dData(); |
|||
webSocketService.pushWarehouse3dBoardData(emptyData); |
|||
lastDataHash.put("warehouse-3d", emptyData.hashCode()); |
|||
} catch (Exception ex) { |
|||
log.error("推送空数据失败: {}", ex.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 从数据库获取智能立体仓库看板数据 |
|||
* |
|||
* @return 智能立体仓库看板数据 |
|||
*/ |
|||
private Map<String, Object> getWarehouse3dBoardDataFromDb() { |
|||
try { |
|||
log.debug("开始从数据库获取智能立体仓库看板数据"); |
|||
|
|||
// 查询任务统计数据 |
|||
//Map<String, Object> taskStats = dashboardDao.queryWarehouseTaskStats(); |
|||
Map<String, Object> taskStats = new HashMap<>(); |
|||
log.debug("任务统计数据: {}", taskStats); |
|||
|
|||
// 查询库位利用率数据 |
|||
//Map<String, Object> storageUtilization = dashboardDao.queryWarehouseStorageUtilization(); |
|||
Map<String, Object> storageUtilization = new HashMap<>(); |
|||
log.debug("库位利用率数据: {}", storageUtilization); |
|||
|
|||
// 查询机器人状态数据 |
|||
//List<Map<String, Object>> robotStatus = dashboardDao.queryWarehouseRobotStatus(); |
|||
List<Map<String, Object>> robotStatus = new ArrayList<>(); |
|||
log.debug("查询到机器人状态数据: {}条", robotStatus != null ? robotStatus.size() : 0); |
|||
|
|||
// 查询AGV状态数据(从TUSK系统获取) |
|||
List<Map<String, Object>> agvStatus = getAgvStatusFromTusk(); |
|||
log.debug("查询到AGV状态数据: {}条", agvStatus != null ? agvStatus.size() : 0); |
|||
|
|||
// 查询领料申请单统计 |
|||
//Map<String, Object> materialRequestStats = dashboardDao.queryWarehouseMaterialRequestStats(); |
|||
Map<String, Object> materialRequestStats = new HashMap<>(); |
|||
log.debug("领料申请单统计: {}", materialRequestStats); |
|||
|
|||
// 查询发货统计 |
|||
//Map<String, Object> shipmentStats = dashboardDao.queryWarehouseShipmentStats(); |
|||
Map<String, Object> shipmentStats = new HashMap<>(); |
|||
log.debug("发货统计: {}", shipmentStats); |
|||
|
|||
// 构造返回数据 |
|||
Map<String, Object> 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<String, Object> createEmptyWarehouse3dData() { |
|||
Map<String, Object> 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<String, Object> 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状态数据 |
|||
* |
|||
* <p><b>数据转换说明:</b></p> |
|||
* <ul> |
|||
* <li>从TUSK获取原始AGV状态</li> |
|||
* <li>转换为看板需要的格式</li> |
|||
* <li>映射状态码为状态文本</li> |
|||
* </ul> |
|||
* |
|||
* @return AGV状态列表 |
|||
*/ |
|||
private List<Map<String, Object>> getAgvStatusFromTusk() { |
|||
List<Map<String, Object>> agvList = new ArrayList<>(); |
|||
|
|||
try { |
|||
// 如果TUSK客户端服务未配置,返回空列表 |
|||
if (tuskClientService == null) { |
|||
log.debug("TUSK客户端服务未配置,跳过AGV状态查询"); |
|||
return agvList; |
|||
} |
|||
|
|||
// 调用TUSK接口获取在线AGV列表 |
|||
TuskResponse<List<AgvStatus>> response = tuskClientService.getOnlineRobots(); |
|||
|
|||
if (!response.isSuccess() || response.getData() == null) { |
|||
log.warn("从TUSK获取AGV状态失败: {}", response.getMsg()); |
|||
return agvList; |
|||
} |
|||
|
|||
// 转换TUSK数据为看板格式 |
|||
List<AgvStatus> tuskAgvList = response.getData(); |
|||
for (AgvStatus agvStatus : tuskAgvList) { |
|||
Map<String, Object> 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 "未知状态"; |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,87 @@ |
|||
package com.gaotao.modules.handlingunit.controller; |
|||
|
|||
import com.gaotao.common.utils.R; |
|||
import com.gaotao.modules.handlingunit.entity.HandlingUnit; |
|||
import com.gaotao.modules.handlingunit.service.HandlingUnitService; |
|||
import com.gaotao.modules.sys.controller.AbstractController; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.web.bind.annotation.*; |
|||
|
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* PDA标签查询控制器 |
|||
* |
|||
* <p><b>主要功能:</b></p> |
|||
* <ul> |
|||
* <li>扫描标签查询HandlingUnit信息</li> |
|||
* <li>显示物料编码、库位、仓库、批次号、wdr等信息</li> |
|||
* </ul> |
|||
* |
|||
* @author System |
|||
* @since 2025-01-23 |
|||
*/ |
|||
@Slf4j |
|||
@RestController |
|||
@RequestMapping("/pda/label") |
|||
public class PdaLabelController extends AbstractController { |
|||
|
|||
@Autowired |
|||
private HandlingUnitService handlingUnitService; |
|||
|
|||
/** |
|||
* @Author System |
|||
* @Description 查询标签信息 |
|||
* @Date 2025/01/23 |
|||
* @Param [Map<String, Object>] |
|||
* @return com.gaotao.common.utils.R |
|||
**/ |
|||
@PostMapping("query") |
|||
public R queryLabelInfo(@RequestBody Map<String, Object> params) { |
|||
try { |
|||
String site = (String) params.get("site"); |
|||
String labelCode = (String) params.get("labelCode"); |
|||
|
|||
log.info("=== 开始查询标签信息 ==="); |
|||
log.info("工厂: {}, 标签编码: {}", site, labelCode); |
|||
|
|||
// 参数验证 |
|||
if (site == null || site.trim().isEmpty()) { |
|||
return R.error("工厂编码不能为空"); |
|||
} |
|||
|
|||
if (labelCode == null || labelCode.trim().isEmpty()) { |
|||
return R.error("标签编码不能为空"); |
|||
} |
|||
|
|||
// 查询HandlingUnit信息 |
|||
HandlingUnit handlingUnit = handlingUnitService.lambdaQuery() |
|||
.eq(HandlingUnit::getSite, site) |
|||
.eq(HandlingUnit::getUnitId, labelCode.trim()) |
|||
.one(); |
|||
|
|||
if (handlingUnit == null) { |
|||
log.warn("标签不存在: site={}, labelCode={}", site, labelCode); |
|||
return R.error("标签不存在"); |
|||
} |
|||
|
|||
log.info("查询到标签信息: unitId={}, partNo={}, locationId={}, warehouseId={}, batchNo={}, wdr={}", |
|||
handlingUnit.getUnitId(), |
|||
handlingUnit.getPartNo(), |
|||
handlingUnit.getLocationId(), |
|||
handlingUnit.getWarehouseId(), |
|||
handlingUnit.getBatchNo(), |
|||
handlingUnit.getWdr()); |
|||
|
|||
log.info("=== 标签信息查询完成 ==="); |
|||
|
|||
return R.ok().put("data", handlingUnit); |
|||
|
|||
} catch (Exception e) { |
|||
log.error("=== 查询标签信息失败 === 错误信息: {}", e.getMessage(), e); |
|||
return R.error("查询失败: " + e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,269 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
|
|||
<mapper namespace="com.gaotao.modules.dashboard.dao.DashboardDao"> |
|||
|
|||
<!-- 查询分切区助力臂数据 --> |
|||
<select id="querySlittingAssistArmData" resultType="map"> |
|||
SELECT |
|||
storage_location AS storageLocation, |
|||
pallet_code AS palletCode, |
|||
picking_location AS pickingLocation, |
|||
material_name AS materialName, |
|||
quantity, |
|||
status |
|||
FROM view_board_slitting_assist_arm |
|||
ORDER BY picking_location ASC |
|||
</select> |
|||
|
|||
<!-- 查询分切区入库数据 --> |
|||
<select id="querySlittingInboundData" resultType="map"> |
|||
SELECT |
|||
storage_location AS storageLocation, |
|||
pallet_code AS palletCode, |
|||
task_type AS taskType, |
|||
status |
|||
FROM view_board_slitting_inbound |
|||
ORDER BY storage_location ASC |
|||
</select> |
|||
|
|||
<!-- ==================== 智能立体仓库看板查询 ==================== --> |
|||
|
|||
<!-- 查询立体仓库任务统计数据 --> |
|||
<select id="queryWarehouseTaskStats" resultType="map"> |
|||
-- 说明:根据实际数据库表结构调整SQL |
|||
-- 这里提供示例结构,需要根据实际的任务表(如wms_task、agv_task等)调整 |
|||
SELECT |
|||
-- 累计任务总数(从任务历史表统计) |
|||
(SELECT COUNT(*) FROM wms_task WHERE is_deleted = '0') AS totalTasks, |
|||
|
|||
-- 月度作业总数(当月任务数) |
|||
(SELECT COUNT(*) FROM wms_task |
|||
WHERE is_deleted = '0' |
|||
AND MONTH(created_date) = MONTH(GETDATE()) |
|||
AND YEAR(created_date) = YEAR(GETDATE())) AS monthlyTasks, |
|||
|
|||
-- 出库作业数(当月) |
|||
(SELECT COUNT(*) FROM wms_task |
|||
WHERE is_deleted = '0' |
|||
AND task_type = 'OUTBOUND' |
|||
AND MONTH(created_date) = MONTH(GETDATE()) |
|||
AND YEAR(created_date) = YEAR(GETDATE())) AS outboundTasks, |
|||
|
|||
-- 入库作业数(当月) |
|||
(SELECT COUNT(*) FROM wms_task |
|||
WHERE is_deleted = '0' |
|||
AND task_type = 'INBOUND' |
|||
AND MONTH(created_date) = MONTH(GETDATE()) |
|||
AND YEAR(created_date) = YEAR(GETDATE())) AS inboundTasks, |
|||
|
|||
-- 出库占比(百分比) |
|||
CASE |
|||
WHEN (SELECT COUNT(*) FROM wms_task |
|||
WHERE is_deleted = '0' |
|||
AND MONTH(created_date) = MONTH(GETDATE()) |
|||
AND YEAR(created_date) = YEAR(GETDATE())) > 0 |
|||
THEN CAST( |
|||
(SELECT COUNT(*) FROM wms_task |
|||
WHERE is_deleted = '0' |
|||
AND task_type = 'OUTBOUND' |
|||
AND MONTH(created_date) = MONTH(GETDATE()) |
|||
AND YEAR(created_date) = YEAR(GETDATE())) * 100.0 / |
|||
(SELECT COUNT(*) FROM wms_task |
|||
WHERE is_deleted = '0' |
|||
AND MONTH(created_date) = MONTH(GETDATE()) |
|||
AND YEAR(created_date) = YEAR(GETDATE())) |
|||
AS INT) |
|||
ELSE 0 |
|||
END AS outboundPercent, |
|||
|
|||
-- 入库占比(百分比) |
|||
CASE |
|||
WHEN (SELECT COUNT(*) FROM wms_task |
|||
WHERE is_deleted = '0' |
|||
AND MONTH(created_date) = MONTH(GETDATE()) |
|||
AND YEAR(created_date) = YEAR(GETDATE())) > 0 |
|||
THEN CAST( |
|||
(SELECT COUNT(*) FROM wms_task |
|||
WHERE is_deleted = '0' |
|||
AND task_type = 'INBOUND' |
|||
AND MONTH(created_date) = MONTH(GETDATE()) |
|||
AND YEAR(created_date) = YEAR(GETDATE())) * 100.0 / |
|||
(SELECT COUNT(*) FROM wms_task |
|||
WHERE is_deleted = '0' |
|||
AND MONTH(created_date) = MONTH(GETDATE()) |
|||
AND YEAR(created_date) = YEAR(GETDATE())) |
|||
AS INT) |
|||
ELSE 0 |
|||
END AS inboundPercent |
|||
</select> |
|||
|
|||
<!-- 查询立体仓库库位利用率数据 --> |
|||
<select id="queryWarehouseStorageUtilization" resultType="map"> |
|||
-- 说明:根据实际库位表(如location、storage_location等)调整SQL |
|||
SELECT |
|||
-- 总库位数 |
|||
(SELECT COUNT(*) FROM location WHERE is_deleted = '0' AND warehouse = 'WAREHOUSE_3D') AS totalSlots, |
|||
|
|||
-- 已使用库位数(有库存的库位) |
|||
(SELECT COUNT(DISTINCT location_code) FROM inventory_stock |
|||
WHERE is_deleted = '0' AND in_stock_flag = 'Y') AS usedSlots, |
|||
|
|||
-- 利用率(百分比,保留1位小数) |
|||
CASE |
|||
WHEN (SELECT COUNT(*) FROM location WHERE is_deleted = '0' AND warehouse = 'WAREHOUSE_3D') > 0 |
|||
THEN CAST( |
|||
(SELECT COUNT(DISTINCT location_code) FROM inventory_stock |
|||
WHERE is_deleted = '0' AND in_stock_flag = 'Y') * 100.0 / |
|||
(SELECT COUNT(*) FROM location WHERE is_deleted = '0' AND warehouse = 'WAREHOUSE_3D') |
|||
AS DECIMAL(5,1)) |
|||
ELSE 0 |
|||
END AS utilizationRate, |
|||
|
|||
-- 钢制托盘数量(根据pallet_type字段统计) |
|||
(SELECT COUNT(*) FROM pallet |
|||
WHERE is_deleted = '0' AND pallet_type = 'STEEL') AS steelPallet, |
|||
|
|||
-- 护边托盘数量 |
|||
(SELECT COUNT(*) FROM pallet |
|||
WHERE is_deleted = '0' AND pallet_type = 'GUARD') AS guardPallet, |
|||
|
|||
-- 平托盘数量 |
|||
(SELECT COUNT(*) FROM pallet |
|||
WHERE is_deleted = '0' AND pallet_type = 'FLAT') AS flatPallet |
|||
</select> |
|||
|
|||
<!-- 查询立体仓库机器人状态数据 --> |
|||
<select id="queryWarehouseRobotStatus" resultType="map"> |
|||
-- 说明:根据实际设备表(如equipment、robot_device等)调整SQL |
|||
SELECT |
|||
id, |
|||
device_name AS name, |
|||
device_status AS status, |
|||
CASE device_status |
|||
WHEN 'WORKING' THEN '工作中' |
|||
WHEN 'IDLE' THEN '空闲' |
|||
WHEN 'CHARGING' THEN '充电中' |
|||
WHEN 'ERROR' THEN '故障' |
|||
ELSE '未知' |
|||
END AS statusText, |
|||
efficiency, |
|||
current_tasks AS tasks |
|||
FROM equipment_device |
|||
WHERE is_deleted = '0' |
|||
AND device_type = 'ROBOT' |
|||
AND warehouse = 'WAREHOUSE_3D' |
|||
ORDER BY id ASC |
|||
</select> |
|||
|
|||
<!-- 查询立体仓库AGV状态数据(已废弃) --> |
|||
<!-- |
|||
注意:AGV状态数据不从数据库查询,而是从TUSK系统实时获取 |
|||
queryWarehouseAgvStatus 方法已在 DashboardPushTask.getAgvStatusFromTusk() 中实现 |
|||
通过调用 TuskClientService.getOnlineRobots() 获取实时AGV状态 |
|||
|
|||
此查询保留空实现仅为避免MyBatis映射错误,实际不应被调用 |
|||
--> |
|||
<select id="queryWarehouseAgvStatus" resultType="map"> |
|||
SELECT |
|||
NULL AS id, |
|||
NULL AS name, |
|||
NULL AS status, |
|||
NULL AS statusText, |
|||
NULL AS battery, |
|||
NULL AS tasks |
|||
WHERE 1=0 |
|||
</select> |
|||
|
|||
<!-- 查询立体仓库领料申请单统计 --> |
|||
<select id="queryWarehouseMaterialRequestStats" resultType="map"> |
|||
-- 说明:根据实际领料申请单表(如material_request、picking_order等)调整SQL |
|||
SELECT |
|||
-- 总数 |
|||
(SELECT COUNT(*) FROM material_request |
|||
WHERE is_deleted = '0' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) AS total, |
|||
|
|||
-- 已完成数 |
|||
(SELECT COUNT(*) FROM material_request |
|||
WHERE is_deleted = '0' |
|||
AND status = 'COMPLETED' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) AS completed, |
|||
|
|||
-- 处理中数 |
|||
(SELECT COUNT(*) FROM material_request |
|||
WHERE is_deleted = '0' |
|||
AND status = 'PROCESSING' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) AS processing, |
|||
|
|||
-- 待处理数 |
|||
(SELECT COUNT(*) FROM material_request |
|||
WHERE is_deleted = '0' |
|||
AND status = 'PENDING' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) AS pending, |
|||
|
|||
-- 完成率(百分比) |
|||
CASE |
|||
WHEN (SELECT COUNT(*) FROM material_request |
|||
WHERE is_deleted = '0' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) > 0 |
|||
THEN CAST( |
|||
(SELECT COUNT(*) FROM material_request |
|||
WHERE is_deleted = '0' |
|||
AND status = 'COMPLETED' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) * 100.0 / |
|||
(SELECT COUNT(*) FROM material_request |
|||
WHERE is_deleted = '0' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) |
|||
AS INT) |
|||
ELSE 0 |
|||
END AS completionRate |
|||
</select> |
|||
|
|||
<!-- 查询立体仓库发货统计 --> |
|||
<select id="queryWarehouseShipmentStats" resultType="map"> |
|||
-- 说明:根据实际发货单表(如shipment_order、delivery_order等)调整SQL |
|||
SELECT |
|||
-- 总数 |
|||
(SELECT COUNT(*) FROM shipment_order |
|||
WHERE is_deleted = '0' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) AS total, |
|||
|
|||
-- 已完成数 |
|||
(SELECT COUNT(*) FROM shipment_order |
|||
WHERE is_deleted = '0' |
|||
AND status = 'COMPLETED' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) AS completed, |
|||
|
|||
-- 处理中数 |
|||
(SELECT COUNT(*) FROM shipment_order |
|||
WHERE is_deleted = '0' |
|||
AND status = 'PROCESSING' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) AS processing, |
|||
|
|||
-- 待处理数 |
|||
(SELECT COUNT(*) FROM shipment_order |
|||
WHERE is_deleted = '0' |
|||
AND status = 'PENDING' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) AS pending, |
|||
|
|||
-- 完成率(百分比) |
|||
CASE |
|||
WHEN (SELECT COUNT(*) FROM shipment_order |
|||
WHERE is_deleted = '0' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) > 0 |
|||
THEN CAST( |
|||
(SELECT COUNT(*) FROM shipment_order |
|||
WHERE is_deleted = '0' |
|||
AND status = 'COMPLETED' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) * 100.0 / |
|||
(SELECT COUNT(*) FROM shipment_order |
|||
WHERE is_deleted = '0' |
|||
AND DATE(created_date) = CAST(GETDATE() AS DATE)) |
|||
AS INT) |
|||
ELSE 0 |
|||
END AS completionRate |
|||
</select> |
|||
|
|||
</mapper> |
|||
|
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue