Browse Source

Merge remote-tracking branch 'origin/master'

master
常熟吴彦祖 5 months ago
parent
commit
9d5f63442c
  1. 6
      build.gradle
  2. 21
      src/main/java/com/gaotao/common/utils/AgvClientUtil.java
  3. 1
      src/main/java/com/gaotao/config/ShiroConfig.java
  4. 68
      src/main/java/com/gaotao/config/WebSocketConfig.java
  5. 138
      src/main/java/com/gaotao/modules/dashboard/dao/DashboardDao.java
  6. 222
      src/main/java/com/gaotao/modules/dashboard/service/DashboardWebSocketService.java
  7. 635
      src/main/java/com/gaotao/modules/dashboard/task/DashboardPushTask.java
  8. 87
      src/main/java/com/gaotao/modules/handlingunit/controller/PdaLabelController.java
  9. 14
      src/main/java/com/gaotao/modules/po/service/impl/PoServiceImpl.java
  10. 269
      src/main/resources/mapper/dashboard/DashboardDao.xml

6
build.gradle

@ -36,6 +36,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-freemarker' implementation 'org.springframework.boot:spring-boot-starter-freemarker'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3' implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// WebSocket
implementation 'org.springframework.boot:spring-boot-starter-websocket'
// //
implementation 'org.springframework.boot:spring-boot-starter-data-ldap' implementation 'org.springframework.boot:spring-boot-starter-data-ldap'
// //
@ -132,7 +134,3 @@ sourceSets {
} }
} }
} }
jar {
enabled = false
}

21
src/main/java/com/gaotao/common/utils/AgvClientUtil.java

@ -313,39 +313,18 @@ public class AgvClientUtil {
*/ */
public Object getOnlineRobot() { public Object getOnlineRobot() {
String url = agvUrl + "/rpc/getOnlineRobot"; String url = agvUrl + "/rpc/getOnlineRobot";
Map<String, Object> request = new HashMap<>();
Long logId = null;
try { try {
// 记录接口调用日志
String requestJson = JSONObject.toJSONString(request);
logId = interfaceCallLogService.logInterfaceCall(
"AgvClientUtil",
"getOnlineRobot",
requestJson,
"55",
null,
"AGV查询在线小车接口"
);
String ifsResponse = HttpUtils.doGet(url, null, null); String ifsResponse = HttpUtils.doGet(url, null, null);
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(ifsResponse); JsonNode jsonNode = mapper.readTree(ifsResponse);
int code = jsonNode.get("code").asInt(); int code = jsonNode.get("code").asInt();
String msg = jsonNode.get("msg").asText(); String msg = jsonNode.get("msg").asText();
if(code != 200){ if(code != 200){
throw new RuntimeException("调用AGV接口失败,错误码:"+code+",错误信息:"+msg); throw new RuntimeException("调用AGV接口失败,错误码:"+code+",错误信息:"+msg);
} }
// 返回数据部分 // 返回数据部分
return jsonNode.get("data"); return jsonNode.get("data");
} catch (Exception e) { } catch (Exception e) {
// 更新接口日志错误信息
if (logId != null) {
interfaceCallLogService.updateCallResult(logId, null, "FAILED", e.getMessage(), null);
}
throw new RuntimeException(e.getMessage()); throw new RuntimeException(e.getMessage());
} }
} }

1
src/main/java/com/gaotao/config/ShiroConfig.java

@ -51,6 +51,7 @@ public class ShiroConfig {
filterMap.put("/api/wms/**", "anon");//wcsrcs反馈信息 filterMap.put("/api/wms/**", "anon");//wcsrcs反馈信息
filterMap.put("/api/agv/**", "anon");//agv反馈信息 filterMap.put("/api/agv/**", "anon");//agv反馈信息
filterMap.put("/api/dashboard/**", "anon");//看板接口 filterMap.put("/api/dashboard/**", "anon");//看板接口
filterMap.put("/ws/dashboard/**", "anon");//看板接口
filterMap.put("/swagger/**", "anon"); filterMap.put("/swagger/**", "anon");
filterMap.put("/v2/api-docs", "anon"); filterMap.put("/v2/api-docs", "anon");
filterMap.put("/swagger-ui.html", "anon"); filterMap.put("/swagger-ui.html", "anon");

68
src/main/java/com/gaotao/config/WebSocketConfig.java

@ -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时
}
}

138
src/main/java/com/gaotao/modules/dashboard/dao/DashboardDao.java

@ -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();
}

222
src/main/java/com/gaotao/modules/dashboard/service/DashboardWebSocketService.java

@ -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);
}
}
}

635
src/main/java/com/gaotao/modules/dashboard/task/DashboardPushTask.java

@ -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 "未知状态";
}
}

87
src/main/java/com/gaotao/modules/handlingunit/controller/PdaLabelController.java

@ -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());
}
}
}

14
src/main/java/com/gaotao/modules/po/service/impl/PoServiceImpl.java

@ -172,8 +172,14 @@ public class PoServiceImpl extends ServiceImpl<PoMapper, PurchaseOrder> implemen
if (partData!=null && !partData.isEmpty()) { if (partData!=null && !partData.isEmpty()) {
shelfLife = partData.getFirst().get("durabilityDays")!=null?(Integer) partData.getFirst().get("durabilityDays"):null; 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(); String receiptNo = receiptDetail.getReceiptNo();
SysUserEntity currentUser = (SysUserEntity) SecurityUtils.getSubject().getPrincipal(); SysUserEntity currentUser = (SysUserEntity) SecurityUtils.getSubject().getPrincipal();
String transType = "CRT"; String transType = "CRT";
@ -184,7 +190,7 @@ public class PoServiceImpl extends ServiceImpl<PoMapper, PurchaseOrder> implemen
transHeader.setTransNo(transNo.getNewTransNo()); transHeader.setTransNo(transNo.getNewTransNo());
transHeader.setTransDate(new Date()); transHeader.setTransDate(new Date());
transHeader.setTransTypeDb(transType); transHeader.setTransTypeDb(transType);
transHeader.setWarehouseId(inData.getWarehouseId());
transHeader.setWarehouseId(warehouseId);
transHeader.setUserId(currentUser.getUserId().toString()); transHeader.setUserId(currentUser.getUserId().toString());
transHeader.setUserName(currentUser.getUserDisplay()); transHeader.setUserName(currentUser.getUserDisplay());
transHeader.setRemark(inData.getRemark()); transHeader.setRemark(inData.getRemark());
@ -522,7 +528,7 @@ public class PoServiceImpl extends ServiceImpl<PoMapper, PurchaseOrder> 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); TransNoControl receiptNoControl = transNoService.getTransNo(inData.getSite(), "PR", 10);
String receiptNo = receiptNoControl.getNewTransNo(); String receiptNo = receiptNoControl.getNewTransNo();
@ -542,7 +548,7 @@ public class PoServiceImpl extends ServiceImpl<PoMapper, PurchaseOrder> implemen
//poReceipt.setDeliveryNoteNo(inData.getPoNo()); // UI和ifs没有返回送货单号 //poReceipt.setDeliveryNoteNo(inData.getPoNo()); // UI和ifs没有返回送货单号
poReceipt.setPrinted("N"); poReceipt.setPrinted("N");
poReceipt.setRemark("PO接收自动创建 - " + inData.getRemark()); poReceipt.setRemark("PO接收自动创建 - " + inData.getRemark());
poReceipt.setWarehouseId(inData.getWarehouseId());
poReceipt.setWarehouseId(warehouseId);
poReceipt.setOutWorkOrderFlag("N"); poReceipt.setOutWorkOrderFlag("N");
poReceipt.setEmailCanSendFlag("N"); poReceipt.setEmailCanSendFlag("N");
poReceipt.setOrderref1(inData.getPoNo()); poReceipt.setOrderref1(inData.getPoNo());

269
src/main/resources/mapper/dashboard/DashboardDao.xml

@ -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>
Loading…
Cancel
Save