自动化立体仓库 - WMS系统
chen.llin
3 天以前 cea1758e1f540e3f5f807951f128b7385b32afe6
agv增加主动查询状态
6个文件已修改
706 ■■■■■ 已修改文件
src/main/java/com/zy/asrs/entity/Task.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/TaskLog.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/AgvScheduler.java 218 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/handler/AgvHandler.java 462 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/constant/ApiInterfaceConstant.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/TaskMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/Task.java
@@ -267,6 +267,13 @@
    @ApiModelProperty(value = "")
    @TableField("error_memo")
    private String errorMemo;
    @ApiModelProperty(value = "")
    @TableField("error_time2")
    private Date errorTime2;
    @ApiModelProperty(value = "")
    @TableField("error_memo2")
    private String errorMemo2;
    @ApiModelProperty(value = "")
    @TableField("ctn_kind")
src/main/java/com/zy/asrs/entity/TaskLog.java
@@ -274,6 +274,14 @@
    @ApiModelProperty(value = "")
    @TableField("error_memo")
    private String errorMemo;
    @ApiModelProperty(value = "")
    @TableField("error_time2")
    private Date errorTime2;
    @ApiModelProperty(value = "")
    @TableField("error_memo2")
    private String errorMemo2;
    @ApiModelProperty(value = "")
    @TableField("ctn_kind")
src/main/java/com/zy/asrs/task/AgvScheduler.java
@@ -1,5 +1,7 @@
package com.zy.asrs.task;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.core.common.Cools;
@@ -11,7 +13,9 @@
import com.zy.asrs.service.WrkMastLogService;
import com.zy.asrs.service.WrkMastService;
import com.zy.asrs.task.handler.AgvHandler;
import com.zy.common.constant.ApiInterfaceConstant;
import com.zy.common.properties.SchedulerProperties;
import com.zy.common.utils.HttpHandler;
import com.zy.system.entity.Config;
import com.zy.system.service.ConfigService;
import lombok.extern.slf4j.Slf4j;
@@ -19,11 +23,14 @@
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -144,6 +151,7 @@
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.warn("定时任务allocateSite:延迟被中断", e);
                    isAllocateSite.set(false);
                    break; // 如果被中断,退出循环
                }
            }
@@ -202,7 +210,6 @@
                        displayTaskId, task.getWrkNo(), task.getIoType(), task.getStaNo());
                boolean processed = agvHandler.callAgv(Collections.singletonList(task));
                // 只有当任务成功处理(成功呼叫AGV,状态从7变为8)时,才更新lastProcessedTaskId
                // 如果任务被跳过(站点被占用等),不更新lastProcessedTaskId,下次会重新尝试
                if (processed) {
                    lastProcessedTaskId = task.getId();
@@ -218,6 +225,7 @@
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.warn("呼叫AGV定时任务:延迟被中断", e);
                    isAllocateSite.set(false);
                    break; // 如果被中断,退出循环
                }
            }
@@ -325,14 +333,16 @@
                    if (agvTask.getIoType() != null &&
                            (agvTask.getIoType() == 1 || agvTask.getIoType() == 10 ||
                                    agvTask.getIoType() == 53 || agvTask.getIoType() == 57)) {
                        // taskId使用工作号(wrk_no),如果工作号为空则使用任务ID
                        String displayTaskId = (agvTask.getWrkNo() != null) ? String.valueOf(agvTask.getWrkNo()) : String.valueOf(agvTask.getId());
                        // 更新AGV任务状态为完成
                        agvTask.setWrkSts(9L);
                        agvTask.setModiTime(now);
                        if (taskService.updateById(agvTask)) {
                            completedTasks.add(agvTask);
                            completedCount++;
                            // taskId使用工作号(wrk_no),如果工作号为空则使用任务ID
                            String displayTaskId = (agvTask.getWrkNo() != null) ? String.valueOf(agvTask.getWrkNo()) : String.valueOf(agvTask.getId());
                            log.info("入库任务工作档已入库成功,完结AGV呼叫单,taskId:{},wrkNo:{},barcode:{}",
                                    displayTaskId, wrkMast.getWrkNo(), wrkMast.getBarcode());
                        }
@@ -577,6 +587,36 @@
                                reason, displayTaskId, agvTask.getWrkNo(), agvTask.getBarcode(), agvTask.getStaNo());
                    }
                }
                // 检查订单创建时间,超过五分钟后才查询AGV订单状态
                Date appeTime = agvTask.getAppeTime();
                boolean shouldCheckAgvStatus = false;
                if (appeTime != null) {
                    long timeDiff = now.getTime() - appeTime.getTime();
                    long fiveMinutesInMillis = 5 * 60 * 1000; // 5分钟
                    if (timeDiff >= fiveMinutesInMillis) {
                        shouldCheckAgvStatus = true;
                    }
                } else {
                    // 如果没有创建时间,默认检查
                    shouldCheckAgvStatus = true;
                }
                // taskId使用工作号(wrk_no),如果工作号为空则使用任务ID
                String displayTaskId = (agvTask.getWrkNo() != null) ? String.valueOf(agvTask.getWrkNo()) : String.valueOf(agvTask.getId());
                // 如果订单创建超过五分钟,查询AGV订单状态
                if (shouldCheckAgvStatus) {
                    String agvOrderStatus = queryAgvOrderStatus(agvTask, displayTaskId);
                    if (agvOrderStatus != null) {
                        // 根据订单状态处理
                        boolean shouldComplete = processAgvOrderStatus(agvTask, agvOrderStatus, displayTaskId, now);
                        if (shouldComplete) {
                            completedTasks.add(agvTask);
                        }
                    }
                }
            }
            // 立即将完成的AGV任务转移到历史表,不保留在Task表中
@@ -597,5 +637,177 @@
        }
    }
    /**
     * 查询AGV订单状态
     * @param agvTask AGV任务
     * @param displayTaskId 显示的任务ID
     * @return 订单状态(Building/Created/Assigned/Failed/Done/Cancelled),如果查询失败返回null
     */
    private String queryAgvOrderStatus(Task agvTask, String displayTaskId) {
        try {
            // 构建订单ID,格式为 "T" + wrkNo
            String orderId = null;
            if (agvTask.getWrkNo() != null) {
                orderId = "T" + agvTask.getWrkNo();
            } else {
                String errorMsg = String.format("查询AGV订单状态失败:任务ID:%s,wrkNo为空", displayTaskId);
                log.warn("查询AGV订单状态失败 - 任务ID:{},{}", displayTaskId, errorMsg);
                agvTask.setErrorMemo(errorMsg);
                agvTask.setErrorTime(new Date());
                taskService.updateById(agvTask);
                return null;
            }
            // 构建请求JSON
            JSONObject requestJson = new JSONObject();
            requestJson.put("entityName", "ContainerTransportOrder");
            requestJson.put("id", orderId);
            String requestBody = requestJson.toJSONString();
            // 构建请求头
            Map<String, Object> headers = new HashMap<>();
            headers.put("xyy-app-id", "seer");
            headers.put("xyy-app-key", "123456");
            // 发送请求
            String response = null;
            try {
                response = new HttpHandler.Builder()
                        .setUri(ApiInterfaceConstant.AGV_IP)
                        .setPath(ApiInterfaceConstant.AGV_FIND_ONE_PATH)
                        .setJson(requestBody)
                        .setHeaders(headers)
                        .build()
                        .doPost();
            } catch (IOException e) {
                String errorMsg = String.format("查询AGV订单状态API调用失败:%s,请求:%s", e.getMessage(), requestBody);
                log.error("查询AGV订单状态失败 - 任务ID:{},订单ID:{},{}", displayTaskId, orderId, errorMsg, e);
                agvTask.setErrorMemo2(errorMsg);
                agvTask.setErrorTime2(new Date());
                taskService.updateById(agvTask);
                return null;
            }
            // 解析响应
            if (response == null || response.trim().isEmpty()) {
                String errorMsg = String.format("查询AGV订单状态API返回为空,请求:%s", requestBody);
                log.warn("查询AGV订单状态失败 - 任务ID:{},订单ID:{},{}", displayTaskId, orderId, errorMsg);
                agvTask.setErrorMemo2(errorMsg);
                agvTask.setErrorTime2(new Date());
                taskService.updateById(agvTask);
                return null;
            }
            try {
                JSONObject responseJson = JSON.parseObject(response);
                // 检查是否有错误码
                String code = responseJson.getString("code");
                if (code != null && "errNoSuchEntity".equals(code)) {
                    // 找不到业务对象,记录到errorMemo但忽略(不阻止后续处理)
                    String errorMsg = String.format("查询AGV订单状态:找不到订单(errNoSuchEntity),请求:%s,响应:%s", requestBody, response);
                    log.debug("查询AGV订单状态:找不到订单 - 任务ID:{},订单ID:{}", displayTaskId, orderId);
                    agvTask.setErrorMemo2(errorMsg);
                    agvTask.setErrorTime2(new Date());
                    taskService.updateById(agvTask);
                    return null;
                }
                // 获取订单信息
                JSONObject entityValue = responseJson.getJSONObject("entityValue");
                if (entityValue != null) {
                    String status = entityValue.getString("status");
                    if (status != null) {
                        log.info("查询AGV订单状态成功 - 任务ID:{},订单ID:{},状态:{}", displayTaskId, orderId, status);
                        return status;
                    } else {
                        String errorMsg = String.format("查询AGV订单状态响应中缺少status字段,请求:%s,响应:%s", requestBody, response);
                        log.warn("查询AGV订单状态失败 - 任务ID:{},订单ID:{},{}", displayTaskId, orderId, errorMsg);
                        agvTask.setErrorMemo2(errorMsg);
                        agvTask.setErrorTime2(new Date());
                        taskService.updateById(agvTask);
                        return null;
                    }
                } else {
                    String errorMsg = String.format("查询AGV订单状态响应中缺少entityValue字段,请求:%s,响应:%s", requestBody, response);
                    log.warn("查询AGV订单状态失败 - 任务ID:{},订单ID:{},{}", displayTaskId, orderId, errorMsg);
                    agvTask.setErrorMemo2(errorMsg);
                    agvTask.setErrorTime2(new Date());
                    taskService.updateById(agvTask);
                    return null;
                }
            } catch (com.alibaba.fastjson.JSONException e) {
                String errorMsg = String.format("解析AGV订单状态响应JSON失败:%s,请求:%s,响应:%s", e.getMessage(), requestBody, response);
                log.error("查询AGV订单状态失败 - 任务ID:{},订单ID:{},{}", displayTaskId, orderId, errorMsg, e);
                agvTask.setErrorMemo2(errorMsg);
                agvTask.setErrorTime2(new Date());
                taskService.updateById(agvTask);
                return null;
            }
        } catch (Exception e) {
            String errorMsg = String.format("查询AGV订单状态异常:%s", e.getMessage());
            log.error("查询AGV订单状态异常 - 任务ID:{},{}", displayTaskId, errorMsg, e);
            agvTask.setErrorMemo2(errorMsg);
            agvTask.setErrorTime2(new Date());
            taskService.updateById(agvTask);
            return null;
        }
    }
    /**
     * 处理AGV订单状态
     * @param agvTask AGV任务
     * @param status 订单状态
     * @param displayTaskId 显示的任务ID
     * @param now 当前时间
     * @return true表示应该完结订单,false表示不应该完结(跳过)
     */
    private boolean processAgvOrderStatus(Task agvTask, String status, String displayTaskId, Date now) {
        if (status == null) {
            return false; // 状态为空,跳过
        }
        switch (status) {
            case "Building":
                // Building=未提交,正常没有此状态,忽略
                log.debug("AGV订单状态为Building(未提交),忽略 - 任务ID:{}", displayTaskId);
                return true; // 继续处理
            case "Created":
                // Created=已提交,等待期间,不处理
                log.debug("AGV订单状态为Created(已提交),等待期间,不处理 - 任务ID:{}", displayTaskId);
                return false; // 不处理,跳过
            case "Assigned":
            case "Assined":
                // Assigned=已派车,正常状态,不处理
                log.debug("AGV订单状态为{}(已派车),正常状态,不处理 - 任务ID:{}", status, displayTaskId);
                return false; // 不处理,跳过
            case "Failed":
            case "Failde":
                // Failed=失败,标记为失败订单
                log.warn("AGV订单状态为{}(失败),标记为失败订单 - 任务ID:{}", status, displayTaskId);
                agvTask.setWrkSts(10L);
                agvTask.setErrorTime(now);
                agvTask.setErrorMemo(String.format("AGV订单状态为%s(失败)", status));
                taskService.updateById(agvTask);
                return false; // 不处理,跳过
            case "Done":
                // Done=已完成,完结订单
                log.info("AGV订单状态为Done(已完成),完结订单 - 任务ID:{}", displayTaskId);
                return true; // 继续处理,完结订单
            case "Cancelled":
                // Cancelled=取消,取消订单
                log.warn("AGV订单状态为Cancelled(取消),取消订单 - 任务ID:{}", displayTaskId);
                agvTask.setWrkSts(10L);
                agvTask.setErrorTime(now);
                agvTask.setErrorMemo("AGV订单状态为Cancelled(取消)");
                taskService.updateById(agvTask);
                return false; // 不处理,跳过
            default:
                // 未知状态,记录日志但继续处理
                log.warn("AGV订单状态未知:{},继续处理 - 任务ID:{}", status, displayTaskId);
                return true; // 继续处理
        }
    }
}
src/main/java/com/zy/asrs/task/handler/AgvHandler.java
@@ -80,13 +80,13 @@
    /**
     * 呼叫AGV
     *
     * <p>
     * 重要:此方法只能从 AgvScheduler.callAgv() 定时任务中调用!
     * 所有AGV呼叫请求必须通过定时任务统一处理,确保:
     * 1. 任务按顺序处理,避免并发冲突
     * 2. 站点分配和AGV呼叫分离,职责清晰
     * 3. 统一的错误处理和重试机制
     *
     *
     * @param taskList 任务列表(通常只包含一个任务)
     * @return 是否成功处理了任务(成功呼叫AGV,状态从7变为8)
     */
@@ -95,16 +95,16 @@
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        boolean calledFromScheduler = false;
        for (StackTraceElement element : stackTrace) {
            if (element.getClassName().contains("AgvScheduler") &&
                element.getMethodName().equals("callAgv")) {
            if (element.getClassName().contains("AgvScheduler") &&
                    element.getMethodName().equals("callAgv")) {
                calledFromScheduler = true;
                break;
            }
        }
        if (!calledFromScheduler) {
            log.error("严重错误:callAgv方法只能从AgvScheduler.callAgv()定时任务中调用!当前调用堆栈:{}",
                Arrays.stream(stackTrace).limit(5).map(StackTraceElement::toString).collect(Collectors.joining("\n")));
            log.error("严重错误:callAgv方法只能从AgvScheduler.callAgv()定时任务中调用!当前调用堆栈:{}",
                    Arrays.stream(stackTrace).limit(5).map(StackTraceElement::toString).collect(Collectors.joining("\n")));
            return false;
        }
@@ -117,21 +117,21 @@
        if (taskList == null || taskList.isEmpty()) {
            return false;
        }
        // 只处理第一个任务
        Task task = taskList.get(0);
        // 呼叫AGV定时任务只处理已分配站点的任务,站点分配由单独的定时任务处理
        String staNo = task.getStaNo();
        String displayTaskId = (task.getWrkNo() != null) ? String.valueOf(task.getWrkNo()) : String.valueOf(task.getId());
        // 检查站点是否有效(不为空、不为空字符串、不为0)
        if (staNo == null || staNo.isEmpty() || staNo.equals("0")) {
            // 没有有效站点,跳过(站点分配由allocateSite定时任务处理)
            log.warn("定时任务callAgv:任务ID:{}没有有效站点分配(sta_no={}),跳过发送,等待分配站点定时任务处理", displayTaskId, staNo);
            return false; // 返回false,表示未成功处理
        }
        // 检查站点是否有效(不为0且存在)
        try {
            Integer siteNo = Integer.parseInt(staNo);
@@ -156,10 +156,10 @@
            taskService.updateById(task);
            return false; // 返回false,让分配站点定时任务重新分配
        }
        log.info("定时任务callAgv:任务ID:{}已有站点分配:{},准备发送AGV命令", displayTaskId, staNo);
        // 检查站点是否有状态8的同类型任务,有则跳过(不清空站点)
        // 规则:最多给每个站点分配1条任务,未完成则等待
        // 判断任务是否完成:通过查询agv工作档(wrk_mast),如果查不到就是完成了
@@ -178,24 +178,24 @@
                ioTypes = Arrays.asList(101, 110, 103, 107);
                taskType = "出库";
            }
            // 通过SQL查询该站点所有AGV任务(包括状态7和8)
            // 规则:最多给每个站点分配1条任务,未完成则等待
            // 判断任务是否完成:通过查询agv工作档(wrk_mast),如果查不到就是完成了
            List<Task> allTasks = taskService.selectList(
                new EntityWrapper<Task>()
                    .eq("sta_no", staNo)
                    .eq("task_type", "agv")
                    .in("wrk_sts", 7L, 8L)
                    .in("io_type", ioTypes)
                    .ne("id", task.getId()) // 排除当前任务本身
                    .eq("is_deleted", 0) // 排除已删除的任务
                    new EntityWrapper<Task>()
                            .eq("sta_no", staNo)
                            .eq("task_type", "agv")
                            .in("wrk_sts", 7L, 8L)
                            .in("io_type", ioTypes)
                            .ne("id", task.getId()) // 排除当前任务本身
                            .eq("is_deleted", 0) // 排除已删除的任务
            );
            int taskCount = allTasks != null ? allTasks.size() : 0;
            log.info("定时任务:任务ID:{},站点{}查询到{}个AGV任务(排除当前任务),开始检查是否有未完成的任务",
                displayTaskId, staNo, taskCount);
            log.info("定时任务:任务ID:{},站点{}查询到{}个AGV任务(排除当前任务),开始检查是否有未完成的任务",
                    displayTaskId, staNo, taskCount);
            // 检查是否有有效的未完成任务(通过查询agv工作档判断)
            boolean hasValidTask = false;
            if (taskCount > 0) {
@@ -203,83 +203,83 @@
                    if (agvTask.getWrkNo() != null) {
                        // 查询工作档,如果查不到就是完成了
                        WrkMast wrkMast = wrkMastService.selectOne(
                            new EntityWrapper<WrkMast>().eq("wrk_no", agvTask.getWrkNo())
                                new EntityWrapper<WrkMast>().eq("wrk_no", agvTask.getWrkNo())
                        );
                        if (wrkMast != null) {
                            // 工作档存在,检查是否已完成
                            Long wrkSts = wrkMast.getWrkSts();
                            Integer ioType = agvTask.getIoType();
                            if (wrkSts != null && ioType != null) {
                                // 入库任务:状态4或5表示完成
                                boolean isCompleted = false;
                                if ((ioType == 1 || ioType == 10 || ioType == 53 || ioType == 57) &&
                                    (wrkSts == 4L || wrkSts == 5L)) {
                                        (wrkSts == 4L || wrkSts == 5L)) {
                                    isCompleted = true;
                                }
                                // 出库任务:状态14或15表示完成
                                else if ((ioType == 101 || ioType == 110 || ioType == 103 || ioType == 107) &&
                                         (wrkSts == 14L || wrkSts == 15L)) {
                                        (wrkSts == 14L || wrkSts == 15L)) {
                                    isCompleted = true;
                                }
                                if (!isCompleted) {
                                    // 工作档存在且未完成,视为有效任务
                                    hasValidTask = true;
                                    log.info("定时任务:任务ID:{},站点{}已有1条未完成的{}AGV任务(工作号:{},工作档状态:{}),跳过当前任务,等待完成",
                                        displayTaskId, staNo, taskType, agvTask.getWrkNo(), wrkSts);
                                    log.info("定时任务:任务ID:{},站点{}已有1条未完成的{}AGV任务(工作号:{},工作档状态:{}),跳过当前任务,等待完成",
                                            displayTaskId, staNo, taskType, agvTask.getWrkNo(), wrkSts);
                                    break;
                                } else {
                                    log.info("定时任务:任务ID:{},站点{}的任务{}(工作号:{})已完成(工作档状态:{}),不算有效任务",
                                        displayTaskId, staNo, agvTask.getId(), agvTask.getWrkNo(), wrkSts);
                                    log.info("定时任务:任务ID:{},站点{}的任务{}(工作号:{})已完成(工作档状态:{}),不算有效任务",
                                            displayTaskId, staNo, agvTask.getId(), agvTask.getWrkNo(), wrkSts);
                                }
                            } else {
                                // 工作档存在但状态未知,视为有效任务
                                hasValidTask = true;
                                log.info("定时任务:任务ID:{},站点{}已有1条{}AGV任务(工作号:{},工作档状态未知),跳过当前任务,等待完成",
                                    displayTaskId, staNo, taskType, agvTask.getWrkNo());
                                log.info("定时任务:任务ID:{},站点{}已有1条{}AGV任务(工作号:{},工作档状态未知),跳过当前任务,等待完成",
                                        displayTaskId, staNo, taskType, agvTask.getWrkNo());
                                break;
                            }
                        } else {
                            // 如果工作档查不到,视为已完成,不算有效任务
                            log.info("定时任务:任务ID:{},站点{}的任务{}(工作号:{})工作档查不到,视为已完成,不算有效任务",
                                displayTaskId, staNo, agvTask.getId(), agvTask.getWrkNo());
                            log.info("定时任务:任务ID:{},站点{}的任务{}(工作号:{})工作档查不到,视为已完成,不算有效任务",
                                    displayTaskId, staNo, agvTask.getId(), agvTask.getWrkNo());
                        }
                    }
                }
            } else {
                log.info("定时任务:任务ID:{},站点{}没有其他AGV任务,可以分配", displayTaskId, staNo);
            }
            // 如果站点有有效的未完成任务,跳过当前任务
            if (hasValidTask) {
                log.warn("定时任务:任务ID:{},站点{}有未完成的{}AGV任务,跳过当前任务,等待完成。下次将重新尝试处理此任务",
                    displayTaskId, staNo, taskType);
                log.warn("定时任务:任务ID:{},站点{}有未完成的{}AGV任务,跳过当前任务,等待完成。下次将重新尝试处理此任务",
                        displayTaskId, staNo, taskType);
                return false; // 返回false,表示未成功处理,下次会重新尝试
            }
            // 检查是否有状态8且已收到AGV确认的任务(用于日志记录)
            List<Task> transportingTasks = taskService.selectList(
                new EntityWrapper<Task>()
                    .eq("sta_no", staNo)
                    .eq("task_type", "agv")
                    .eq("wrk_sts", 8L) // 只检查正在搬运(8)的任务
                    .isNotNull("plc_str_time") // 只检查已收到AGV确认的任务(plc_str_time不为空)
                    .in("io_type", ioTypes)
                    .ne("id", task.getId()) // 排除当前任务本身
                    .eq("is_deleted", 0) // 排除已删除的任务
                    new EntityWrapper<Task>()
                            .eq("sta_no", staNo)
                            .eq("task_type", "agv")
                            .eq("wrk_sts", 8L) // 只检查正在搬运(8)的任务
                            .isNotNull("plc_str_time") // 只检查已收到AGV确认的任务(plc_str_time不为空)
                            .in("io_type", ioTypes)
                            .ne("id", task.getId()) // 排除当前任务本身
                            .eq("is_deleted", 0) // 排除已删除的任务
            );
            // 检查并自动结束已完成工作档的AGV任务
            int originalCount = transportingTasks.size();
            List<Task> validTransportingTasks = checkAndCompleteFinishedTasks(transportingTasks, taskType);
            int completedCount = originalCount - validTransportingTasks.size();
            if (completedCount > 0) {
                log.info("定时任务:站点{}自动结束了{}个已完成工作档的AGV任务,剩余{}个正在搬运的任务",
                    staNo, completedCount, validTransportingTasks.size());
                log.info("定时任务:站点{}自动结束了{}个已完成工作档的AGV任务,剩余{}个正在搬运的任务",
                        staNo, completedCount, validTransportingTasks.size());
            }
            if (!validTransportingTasks.isEmpty()) {
                List<Integer> transportingTaskIds = validTransportingTasks.stream()
                        .map(Task::getWrkNo)
@@ -298,7 +298,7 @@
                            .append(",创建时间=").append(t.getAppeTime() != null ? t.getAppeTime().toString() : "未知")
                            .append(")");
                }
                log.info("定时任务:站点{}有{}个正在搬运的{}AGV任务(工作号:{}),跳过当前任务ID:{},下次将重新尝试。任务详情:{}",
                log.info("定时任务:站点{}有{}个正在搬运的{}AGV任务(工作号:{}),跳过当前任务ID:{},下次将重新尝试。任务详情:{}",
                        staNo, validTransportingTasks.size(), taskType, transportingTaskIds, displayTaskId, taskDetailInfo.toString());
                return false; // 返回false,表示未成功处理,下次会重新尝试
            } else if (completedCount > 0) {
@@ -306,9 +306,9 @@
                log.info("定时任务:站点{}的所有占用任务已自动结束,站点已释放,可以分配给当前任务ID:{}", staNo, task.getId());
            }
        }
        log.info("定时任务:任务ID:{},站点{}检查通过,准备发送AGV命令", displayTaskId, staNo);
        // 呼叫agv
        String response = "";
        boolean success = false;
@@ -332,32 +332,24 @@
                break;
            default:
        }
        String body = getRequest(task,namespace);
        String body = getRequest(task, namespace);
        // 获取当前重试次数
        int currentRetryCount = getRetryCount(task);
        int maxRetryCount = agvProperties.getCallRetry().getMaxRetryCount();
        boolean retryEnabled = agvProperties.getCallRetry().isEnabled();
            // 如果重试次数已达到最大值,跳过本次发送
            if (retryEnabled && currentRetryCount >= maxRetryCount) {
                // log.warn("{}呼叫agv搬运 - 任务ID:{}已达到最大重试次数({}),停止重试",
                //         namespace, task.getId(), maxRetryCount);
                // 记录最终失败信息
                task.setErrorTime(new Date());
                task.setErrorMemo(String.format("AGV呼叫失败,已达到最大重试次数(%d次)", maxRetryCount));
                taskService.updateById(task);
                return true; // 已达到最大重试次数,不再重试,返回true表示已处理(虽然失败)
            }
        // 打印请求信息(包含重试次数)
        // if (currentRetryCount > 0) {
        //     log.info("{}呼叫agv搬运(第{}次重试) - 请求地址:{}", namespace, currentRetryCount + 1, url);
        // } else {
        //     log.info("{}呼叫agv搬运 - 请求地址:{}", namespace, url);
        // }
        // log.info("{}呼叫agv搬运 - 请求参数:{}", namespace, body);
        // 如果重试次数已达到最大值,跳过本次发送
        if (retryEnabled && currentRetryCount >= maxRetryCount) {
            // log.warn("{}呼叫agv搬运 - 任务ID:{}已达到最大重试次数({}),停止重试",
            //         namespace, task.getId(), maxRetryCount);
            // 记录最终失败信息
            task.setErrorTime(new Date());
            task.setErrorMemo(String.format("AGV呼叫失败,已达到最大重试次数(%d次)", maxRetryCount));
            taskService.updateById(task);
            return true; // 已达到最大重试次数,不再重试,返回true表示已处理(虽然失败)
        }
        boolean result = false; // 默认返回false,表示未成功处理
        try {
            // 使用仙工M4接口
@@ -368,23 +360,16 @@
                    .build()
                    .doPost();
            // 打印返回参数
             log.info("{}呼叫agv搬运,请求参数「{}」 - 返回参数:{}", namespace,body, response);
            log.info("仙工-{}呼叫agv搬运,请求参数「{}」 - 返回参数:{}", namespace, body, response);
            // 检查响应是否为空
            if (response == null || response.trim().isEmpty()) {
                String errorMsg = "AGV接口返回为空";
                log.warn("定时任务:{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, displayTaskId, errorMsg);
                handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount);
                // 如果达到最大重试次数,返回true表示已处理(虽然失败)
                // 否则返回false,让定时任务重新尝试
                if (retryEnabled && currentRetryCount >= maxRetryCount) {
                    result = true; // 已达到最大重试次数,返回true表示已处理(虽然失败)
                    log.info("定时任务:任务ID:{},AGV呼叫失败且已达到最大重试次数({}次),标记为已处理,不再重试",
                        displayTaskId, maxRetryCount);
                } else {
                    result = false; // 返回false,让定时任务重新尝试
                    log.info("定时任务:任务ID:{},下次将重新尝试发送AGV命令", displayTaskId);
                }
                task.setWrkSts(10L);
                task.setErrorTime(new Date());
                task.setErrorMemo(String.format("AGV接口返回为空"));
                taskService.updateById(task);
            } else {
                // 尝试解析JSON响应,捕获JSON解析异常
                JSONObject jsonObject = null;
@@ -394,46 +379,12 @@
                    // JSON解析失败,响应可能不是有效的JSON格式(如"Server Error"等)
                    String errorMsg = String.format("AGV接口返回非JSON格式响应,响应内容:%s,解析错误:%s", response, e.getMessage());
                    log.error("定时任务:{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, displayTaskId, errorMsg);
                    // 服务器错误时,标记站点为不可用,清空站点分配,不再为当前任务分配站点
                    try {
                        Integer siteNo = Integer.parseInt(staNo);
                        // 查询站点信息
                        List<BasDevp> basDevpList = basDevpMapper.selectList(new EntityWrapper<BasDevp>().eq("dev_no", siteNo));
                        if (basDevpList != null && !basDevpList.isEmpty()) {
                            BasDevp basDevp = basDevpList.get(0);
                            // 标记站点为不可用(设置canining='N')
                            basDevp.setCanining("N");
                            basDevpMapper.updateById(basDevp);
                            log.warn("定时任务:任务ID:{},AGV接口返回服务器错误,已标记站点{}为不可用(canining='N')",
                                displayTaskId, siteNo);
                            // 减少站点的入库任务数(之前分配站点时已经增加了in_qty)
                            basDevpMapper.decrementInQty(siteNo);
                            log.debug("定时任务:任务ID:{},站点{}的in_qty已减少", displayTaskId, siteNo);
                        }
                    } catch (Exception ex) {
                        log.error("定时任务:任务ID:{},标记站点{}为不可用时发生异常:{}", displayTaskId, staNo, ex.getMessage());
                    }
                    // 清空当前任务的站点分配
                    log.warn("定时任务:任务ID:{},AGV接口返回服务器错误,清空站点分配:{},不再为当前任务分配站点",
                        displayTaskId, staNo);
                    task.setStaNo(null);
                    taskService.updateById(task);
                    // 标记任务为失败,不再尝试分配
                    task.setWrkSts(10L);
                    task.setErrorTime(new Date());
                    task.setErrorMemo(String.format("AGV接口返回服务器错误,站点已标记为不可用:%s", errorMsg));
                    task.setErrorMemo(errorMsg);
                    taskService.updateById(task);
                    handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount);
                    // 服务器错误时,不再尝试分配,直接标记为已处理
                    result = true; // 返回true表示已处理(虽然失败),不再尝试分配
                    log.info("定时任务:任务ID:{},AGV呼叫失败(服务器错误),站点已标记为不可用,任务已标记为失败,不再尝试分配",
                        displayTaskId);
                }
                // 如果JSON解析成功,继续处理
                if (jsonObject != null) {
                    Integer code = jsonObject.getInteger("code");
@@ -444,13 +395,15 @@
                        Long currentStatus = task.getWrkSts();
                        if (currentStatus == null || currentStatus != 8L) {
                            task.setWrkSts(8L);
                            log.info("定时任务:{}呼叫agv搬运成功 - 任务ID:{},状态从{}更新为8", namespace, displayTaskId, currentStatus);
                        } else {
                            log.info("定时任务:{}呼叫agv搬运成功(重试) - 任务ID:{},状态保持为8", namespace, displayTaskId);
                        }
                        task.setLocNo(task.getStaNo());
                        task.setMemo(clearRetryInfo(task.getMemo())); // 清除重试信息
                        task.setErrorTime(null);
                        task.setErrorMemo(null);
                        task.setErrorTime(new Date());
                        task.setErrorMemo(jsonObject.toJSONString());
                        taskService.updateById(task);
                        log.info("定时任务:{}呼叫agv搬运成功 - 任务ID:{}", namespace, displayTaskId);
                        result = true; // 返回true,表示成功处理
@@ -458,62 +411,29 @@
                        String message = jsonObject.getString("message");
                        String errorMsg = String.format("错误码:%s,错误信息:%s", code, message);
                        log.warn("定时任务:{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, displayTaskId, errorMsg);
                        // 检查是否是站点或库位相关的错误,如果是,清空站点分配,让定时任务重新分配
                        boolean shouldReallocateSite = false;
                        if (message != null) {
                            String lowerMessage = message.toLowerCase();
                            // 库位不存在、站点不存在等错误,应该重新分配站点
                            if (lowerMessage.contains("库位不存在") ||
                                lowerMessage.contains("站点不存在") ||
                                lowerMessage.contains("位置不存在") ||
                                lowerMessage.contains("库位无效") ||
                                lowerMessage.contains("站点无效")) {
                                shouldReallocateSite = true;
                            }
                        }
                        if (shouldReallocateSite) {
                            // 清空站点分配,让定时任务重新分配站点
                            log.warn("定时任务:任务ID:{},AGV呼叫失败({}),清空站点分配:{},下次将重新分配站点",
                                displayTaskId, errorMsg, staNo);
                            task.setStaNo(null);
                            taskService.updateById(task);
                        }
                        handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount);
                        // 如果达到最大重试次数,返回true表示已处理(虽然失败)
                        // 否则返回false,让定时任务重新尝试(如果站点被清空,会重新分配站点;如果站点未清空,会重新发送AGV)
                        if (retryEnabled && currentRetryCount >= maxRetryCount) {
                            result = true; // 已达到最大重试次数,返回true表示已处理(虽然失败)
                            log.info("定时任务:任务ID:{},AGV呼叫失败且已达到最大重试次数({}次),标记为已处理,不再重试",
                                displayTaskId, maxRetryCount);
                        } else {
                            result = false; // 返回false,让定时任务重新尝试(重新分配站点或重新发送AGV)
                            if (shouldReallocateSite) {
                                log.info("定时任务:任务ID:{},站点已清空,下次将重新分配站点", displayTaskId);
                            } else {
                                log.info("定时任务:任务ID:{},下次将重新尝试发送AGV命令", displayTaskId);
                            }
                        }
                        task.setWrkSts(10L);
                        task.setLocNo(task.getStaNo());
                        task.setErrorTime(new Date());
                        task.setErrorMemo(jsonObject.toJSONString());
                        taskService.updateById(task);
                    }
                }
            }
        } catch (Exception e) {
            String errorMsg = "异常信息:" + e.getMessage();
            log.error("定时任务:{}呼叫agv搬运异常 - 任务ID:{},请求地址:{},请求参数:{},{}",
            log.error("定时任务:{}呼叫agv搬运异常 - 任务ID:{},请求地址:{},请求参数:{},{}",
                    namespace, displayTaskId, url, body, errorMsg, e);
            handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount);
            // 如果达到最大重试次数,返回true表示已处理(虽然失败)
            // 否则返回false,让定时任务重新尝试
            if (retryEnabled && currentRetryCount >= maxRetryCount) {
                result = true; // 已达到最大重试次数,返回true表示已处理(虽然失败)
                log.info("定时任务:任务ID:{},AGV呼叫异常且已达到最大重试次数({}次),标记为已处理,不再重试",
                    displayTaskId, maxRetryCount);
            if (errorMsg.contains("connect timed out")) {
                log.error("定时任务:{}呼叫agv搬运异常 - 任务ID:{},网络请求超时",
                        namespace, displayTaskId);
            } else {
                result = false; // 返回false,让定时任务重新尝试
                log.info("定时任务:任务ID:{},下次将重新尝试发送AGV命令", displayTaskId);
                task.setWrkSts(10L);
                task.setLocNo(task.getStaNo());
                task.setErrorTime(new Date());
                task.setErrorMemo(errorMsg);
                taskService.updateById(task);
            }
        } finally {
            try {
@@ -536,15 +456,16 @@
    /**
     * 处理AGV呼叫失败的情况
     * @param task 任务对象
     * @param namespace 命名空间(入库/出库/转移)
     * @param errorMsg 错误信息
     * @param retryEnabled 是否启用重试
     * @param maxRetryCount 最大重试次数
     *
     * @param task              任务对象
     * @param namespace         命名空间(入库/出库/转移)
     * @param errorMsg          错误信息
     * @param retryEnabled      是否启用重试
     * @param maxRetryCount     最大重试次数
     * @param currentRetryCount 当前重试次数
     */
    private void handleCallFailure(Task task, String namespace, String errorMsg,
                                    boolean retryEnabled, int maxRetryCount, int currentRetryCount) {
    private void handleCallFailure(Task task, String namespace, String errorMsg,
                                   boolean retryEnabled, int maxRetryCount, int currentRetryCount) {
        if (retryEnabled && currentRetryCount < maxRetryCount) {
            // 增加重试次数
            int newRetryCount = currentRetryCount + 1;
@@ -571,8 +492,9 @@
    /**
     * 检查并自动结束已完成工作档的AGV任务
     * 如果任务对应的工作档已经完成(入库成功),则自动结束该AGV任务
     *
     * @param transportingTasks 正在搬运的任务列表
     * @param taskTypeName 任务类型名称(用于日志)
     * @param taskTypeName      任务类型名称(用于日志)
     * @return 仍然有效的正在搬运的任务列表(已完成的已被移除)
     */
    @Transactional(rollbackFor = Exception.class)
@@ -580,66 +502,66 @@
        if (transportingTasks == null || transportingTasks.isEmpty()) {
            return transportingTasks;
        }
        List<Task> validTasks = new ArrayList<>();
        Date now = new Date();
        for (Task agvTask : transportingTasks) {
            boolean isCompleted = false;
            String reason = "";
            // 检查工作档是否存在
            WrkMast wrkMast = null;
            if (agvTask.getWrkNo() != null) {
                wrkMast = wrkMastService.selectOne(
                    new EntityWrapper<WrkMast>().eq("wrk_no", agvTask.getWrkNo())
                        new EntityWrapper<WrkMast>().eq("wrk_no", agvTask.getWrkNo())
                );
            }
            // 检查历史档是否存在
            WrkMastLog wrkMastLog = null;
            if (agvTask.getWrkNo() != null) {
                wrkMastLog = wrkMastLogService.selectOne(
                    new EntityWrapper<WrkMastLog>().eq("wrk_no", agvTask.getWrkNo())
                        new EntityWrapper<WrkMastLog>().eq("wrk_no", agvTask.getWrkNo())
                );
            }
            // 如果通过wrk_no没找到,且有条码,则通过条码查询
            if (wrkMastLog == null && !Cools.isEmpty(agvTask.getBarcode())) {
                List<WrkMastLog> logList = wrkMastLogService.selectList(
                    new EntityWrapper<WrkMastLog>().eq("barcode", agvTask.getBarcode())
                        new EntityWrapper<WrkMastLog>().eq("barcode", agvTask.getBarcode())
                );
                if (!logList.isEmpty()) {
                    wrkMastLog = logList.get(0);
                }
            }
            // 如果工作档存在,检查是否已完成
            if (wrkMast != null) {
                Long wrkSts = wrkMast.getWrkSts();
                Integer ioType = agvTask.getIoType();
                if (wrkSts != null && ioType != null) {
                    // 入库任务:状态4(入库完成)或5(库存更新完成)
                    if ((ioType == 1 || ioType == 10 || ioType == 53 || ioType == 57) &&
                        (wrkSts == 4L || wrkSts == 5L)) {
                            (wrkSts == 4L || wrkSts == 5L)) {
                        isCompleted = true;
                        reason = String.format("工作档已完成,状态:%d", wrkSts);
                    }
                    // 出库任务:状态14(已出库未确认)或15(出库更新完成)
                    else if ((ioType == 101 || ioType == 110 || ioType == 103 || ioType == 107) &&
                             (wrkSts == 14L || wrkSts == 15L)) {
                            (wrkSts == 14L || wrkSts == 15L)) {
                        isCompleted = true;
                        reason = String.format("工作档已完成,状态:%d", wrkSts);
                    }
                }
            }
            // 1. 如果工作档进入历史档,立即结束AGV任务(只要历史档存在就结束)
            if (!isCompleted && wrkMastLog != null) {
                isCompleted = true;
                reason = String.format("工作档已转历史档,立即结束AGV任务,历史档状态:%d", wrkMastLog.getWrkSts());
            }
            // 如果已完成,更新AGV任务状态为完成
            if (isCompleted) {
                agvTask.setWrkSts(9L);
@@ -647,8 +569,8 @@
                if (taskService.updateById(agvTask)) {
                    // taskId使用工作号(wrk_no),如果工作号为空则使用任务ID
                    String displayTaskId = (agvTask.getWrkNo() != null) ? String.valueOf(agvTask.getWrkNo()) : String.valueOf(agvTask.getId());
                    log.info("{},自动结束AGV任务,taskId:{},wrkNo:{},barcode:{},站点:{},释放站点供新任务使用",
                        reason, displayTaskId, agvTask.getWrkNo(), agvTask.getBarcode(), agvTask.getStaNo());
                    log.info("{},自动结束AGV任务,taskId:{},wrkNo:{},barcode:{},站点:{},释放站点供新任务使用",
                            reason, displayTaskId, agvTask.getWrkNo(), agvTask.getBarcode(), agvTask.getStaNo());
                    // 转移到历史表(会自动减少站点的in_qty)
                    try {
                        moveTaskToHistory(Collections.singletonList(agvTask));
@@ -657,8 +579,8 @@
                        log.error("自动结束AGV任务后转移历史表失败,taskId:{}", displayTaskId, e);
                    }
                } else {
                    log.error("自动结束AGV任务失败,更新任务状态失败,taskId:{}",
                        (agvTask.getWrkNo() != null) ? String.valueOf(agvTask.getWrkNo()) : String.valueOf(agvTask.getId()));
                    log.error("自动结束AGV任务失败,更新任务状态失败,taskId:{}",
                            (agvTask.getWrkNo() != null) ? String.valueOf(agvTask.getWrkNo()) : String.valueOf(agvTask.getId()));
                }
            } else {
                // 任务仍然有效,保留在列表中
@@ -667,21 +589,21 @@
                if (wrkMast == null && wrkMastLog == null) {
                    log.debug("任务ID:{}(站点:{})仍然有效:工作档和历史档都不存在,可能工作档还未创建", displayTaskId, agvTask.getStaNo());
                } else if (wrkMast != null) {
                    log.debug("任务ID:{}(站点:{})仍然有效:工作档状态={},任务类型={},未达到完成条件",
                        displayTaskId, agvTask.getStaNo(), wrkMast.getWrkSts(), agvTask.getIoType());
                    log.debug("任务ID:{}(站点:{})仍然有效:工作档状态={},任务类型={},未达到完成条件",
                            displayTaskId, agvTask.getStaNo(), wrkMast.getWrkSts(), agvTask.getIoType());
                }
                validTasks.add(agvTask);
            }
        }
        return validTasks;
    }
    /**
     * 从memo字段中获取重试次数
     * memo格式:如果包含"retryCount:数字",则返回该数字,否则返回0
     *
     * @param task 任务对象
     * @return 重试次数
     */
@@ -711,7 +633,8 @@
    /**
     * 更新memo字段中的重试次数
     * @param memo 原始memo内容
     *
     * @param memo       原始memo内容
     * @param retryCount 新的重试次数
     * @return 更新后的memo内容
     */
@@ -731,6 +654,7 @@
    /**
     * 清除memo字段中的重试信息
     *
     * @param memo 原始memo内容
     * @return 清除后的memo内容
     */
@@ -800,15 +724,16 @@
    /**
     * 为任务分配站点(定时任务中调用)
     * 注意:只会分配一个站点,找到第一个符合条件的站点就分配并退出
     *
     * @param task 任务对象
     * @return 如果无法分配站点,返回错误信息;如果分配成功,返回null并更新task的staNo
     */
    @Transactional(rollbackFor = Exception.class)
    public  String allocateSiteForTask(Task task) {
    public String allocateSiteForTask(Task task) {
        // taskId使用工作号(wrk_no),如果工作号为空则使用任务ID
        String displayTaskId = (task.getWrkNo() != null) ? String.valueOf(task.getWrkNo()) : String.valueOf(task.getId());
        log.debug("开始为任务ID:{}分配站点,任务类型:{},机器人组:{}",
            displayTaskId, task.getIoType(), task.getInvWh());
                displayTaskId, task.getIoType(), task.getInvWh());
        // 根据任务的invWh(机器人组)判断是东侧还是西侧
        String robotGroup = task.getInvWh();
        List<String> targetStations;
@@ -841,65 +766,27 @@
                .collect(Collectors.toList());
        log.info("任务ID:{},{}站点组配置的站点:{},共{}个站点",
            displayTaskId, groupKey.equals("east") ? agvProperties.getEastDisplayName() : agvProperties.getWestDisplayName(),
            targetStations, targetStations.size());
        // 判断能入站点(in_enable="Y"表示能入),排除dev_no=0的无效站点
        List<BasDevp> allDevList = basDevpMapper.selectList(
                new EntityWrapper<BasDevp>()
                        .in("dev_no", siteIntList)
                        .ne("dev_no", 0) // 排除dev_no=0的无效站点
        );
        // 记录所有站点的状态信息
        StringBuilder siteStatusInfo = new StringBuilder();
        for (BasDevp dev : allDevList) {
            if (siteStatusInfo.length() > 0) {
                siteStatusInfo.append("; ");
            }
            siteStatusInfo.append("站点").append(dev.getDevNo())
                    .append("(in_enable=").append(dev.getInEnable())
                    .append(",canining=").append(dev.getCanining()).append(")");
        }
        log.info("任务ID:{},候选站点状态:{}", displayTaskId, siteStatusInfo.toString());
        List<Integer> sites = allDevList.stream()
                .filter(dev -> "Y".equals(dev.getInEnable()))
                .map(BasDevp::getDevNo)
                .filter(devNo -> devNo != null && devNo != 0) // 再次过滤,确保不为null或0
                .collect(Collectors.toList());
        // 检查是否有站点不可用,如果有,说明需要在可用的站点之间平均分配
        List<Integer> unavailableSites = new ArrayList<>(siteIntList);
        unavailableSites.removeAll(sites);
        if (!unavailableSites.isEmpty()) {
            log.info("任务ID:{},{}站点组中有{}个站点不可用(in_enable!='Y'):{},将在{}个可用站点之间平均分配",
                displayTaskId, groupKey.equals("east") ? agvProperties.getEastDisplayName() : agvProperties.getWestDisplayName(),
                unavailableSites.size(), unavailableSites, sites.size());
        }
                targetStations, targetStations.size());
        if (sites.isEmpty()) {
            String errorMsg = "没有能入站点(in_enable='Y')";
            log.warn("任务ID:{},{},候选站点列表:{},站点状态:{}", displayTaskId, errorMsg, targetStations, siteStatusInfo.toString());
            return errorMsg;
        }
        // 先检查站点配置(canining="Y"可入),排除dev_no=0的无效站点
        List<BasDevp> devListWithConfig = basDevpMapper.selectList(new EntityWrapper<BasDevp>()
                .in("dev_no", sites)
                .in("dev_no", siteIntList)
                .eq("in_enable", "Y")
                .eq("canining", "Y")
                .eq("loading", "N")
                .ne("dev_no", 0) // 排除dev_no=0的无效站点
        );
        if (devListWithConfig==null || devListWithConfig.isEmpty()) {
            log.warn("任务ID:{}没有可入站点(站点未开通可入允许:canining='Y'),暂不分配站点,等待配置开通。能入站点列表:{}",
                    displayTaskId, sites);
        if (devListWithConfig == null || devListWithConfig.isEmpty()) {
            log.warn("任务ID:{}没有可入站点(站点未开通可入允许:canining='Y'),暂不分配站点,等待配置开通。站点列表:{}",
                    displayTaskId, targetStations);
            return null;
        }
            // 先按规则排序(入库任务数排序)
            devListWithConfig.sort(Comparator.comparing(BasDevp::getInQty));
        // 先按规则排序(入库任务数排序)
        devListWithConfig.sort(Comparator.comparing(BasDevp::getInQty));
        // 根据任务类型确定要检查的io_type列表
        Integer taskIoType = task.getIoType();
        List<Integer> checkIoTypes = null;
@@ -921,14 +808,14 @@
        final List<String> allocatedSiteNos;
        if (checkIoTypes != null && !checkIoTypes.isEmpty()) {
            List<Task> allocatedTasks = taskService.selectList(
                new EntityWrapper<Task>()
                    .eq("task_type", "agv")
                    .in("wrk_sts", 7L, 8L) // 待呼叫AGV和正在搬运的任务
                    .in("io_type", checkIoTypes)
                    .isNotNull("sta_no")
                    .ne("sta_no", "")
                    .ne("sta_no", "0")
                    .andNew("(is_deleted = 0)")
                    new EntityWrapper<Task>()
                            .eq("task_type", "agv")
                            .in("wrk_sts", 7L, 8L) // 待呼叫AGV和正在搬运的任务
                            .in("io_type", checkIoTypes)
                            .isNotNull("sta_no")
                            .ne("sta_no", "")
                            .ne("sta_no", "0")
                            .andNew("(is_deleted = 0)")
            );
            // 获取已分配的站点编号列表
            allocatedSiteNos = allocatedTasks.stream()
@@ -947,8 +834,8 @@
                    return !allocatedSiteNos.contains(staNo);
                })
                .collect(Collectors.toList());
        List<BasDevp> unallocatedSites2= new ArrayList<>();
        for(int i=0;devListWithConfig.size()>i;i++){
        List<BasDevp> unallocatedSites2 = new ArrayList<>();
        for (int i = 0; devListWithConfig.size() > i; i++) {
            unallocatedSites2.add(devListWithConfig.get(i));
        }
//        if(unallocatedSites==null || unallocatedSites.isEmpty()){
@@ -966,7 +853,7 @@
                allocatedSitesInfo.append("站点").append(staNo).append("已被分配");
            }
            log.warn("任务ID:{},所有可用站点都已被分配,暂不分配站点,等待下次定时任务再尝试。已分配站点:{}",
                displayTaskId, allocatedSitesInfo.length() > 0 ? allocatedSitesInfo.toString() : "无详细信息");
                    displayTaskId, allocatedSitesInfo.length() > 0 ? allocatedSitesInfo.toString() : "无详细信息");
            return null; // 返回null,表示暂不分配,等待下次定时任务再尝试
        }
@@ -990,16 +877,16 @@
            orderedSites.addAll(unallocatedSites.subList(roundRobinStartIndex, unallocatedSites.size()));
            orderedSites.addAll(unallocatedSites.subList(0, roundRobinStartIndex));
            log.info("任务ID:{},使用轮询分配策略,站点组:{},轮询起始索引:{},候选站点:{}(共{}个未分配站点)",
                displayTaskId, groupKey, roundRobinStartIndex,
                unallocatedSites.stream().map(d -> String.valueOf(d.getDevNo())).collect(Collectors.joining(",")),
                unallocatedSites.size());
                    displayTaskId, groupKey, roundRobinStartIndex,
                    unallocatedSites.stream().map(d -> String.valueOf(d.getDevNo())).collect(Collectors.joining(",")),
                    unallocatedSites.size());
        } else if (unallocatedSites.size() > 1 && enableRoundRobin && "random".equals(strategy)) {
            // 随机分配:先随机排序未分配站点
            List<BasDevp> shuffledSites = new ArrayList<>(unallocatedSites);
            Collections.shuffle(shuffledSites);
            orderedSites.addAll(shuffledSites);
            log.info("任务ID:{},使用随机分配策略,候选站点:{}",
                displayTaskId, unallocatedSites.stream().map(d -> String.valueOf(d.getDevNo())).collect(Collectors.joining(",")));
                    displayTaskId, unallocatedSites.stream().map(d -> String.valueOf(d.getDevNo())).collect(Collectors.joining(",")));
        } else {
            // 默认:按入库任务数排序(已经排序好了)
            orderedSites = unallocatedSites;
@@ -1012,8 +899,8 @@
        String staNo = String.valueOf(endSite);
        log.info("任务ID:{},从{}个未分配站点中选择站点{}(入库任务数:{}),候选站点:{}",
            displayTaskId, orderedSites.size(), staNo, selectedSite.getInQty(),
            orderedSites.stream().map(d -> String.valueOf(d.getDevNo())).collect(Collectors.joining(",")));
                displayTaskId, orderedSites.size(), staNo, selectedSite.getInQty(),
                orderedSites.stream().map(d -> String.valueOf(d.getDevNo())).collect(Collectors.joining(",")));
        // 如果使用轮询策略且成功分配站点,递增轮询计数器(确保下次从下一个站点开始)
        if (roundRobinCounter != null && unallocatedSites.size() > 1) {
@@ -1042,20 +929,22 @@
        log.info("任务ID:{}已分配站点:{},机器人组:{},任务类型:{}", displayTaskId, endSite, robotGroup, taskTypeName);
        return null; // 分配成功,返回null
    }
    /**
     * 根据站点编号判断机器人组
     *
     * @param staNo 站点编号
     * @return 机器人组名称
     */
        private String determineRobotGroupByStation(String staNo) {
    private String determineRobotGroupByStation(String staNo) {
        if (staNo == null || staNo.isEmpty()) {
            return agvProperties.getRobotGroupEast(); // 默认使用东侧机器人组
        }
        // 从配置中获取站点列表
        Set<String> eastStations = new HashSet<>(agvProperties.getEastStations());
        Set<String> westStations = new HashSet<>(agvProperties.getWestStations());
        // 判断站点属于哪一侧
        if (eastStations.contains(staNo)) {
            return agvProperties.getRobotGroupEast(); // 东侧机器人
@@ -1084,7 +973,7 @@
        // 批量删除原任务
        List<Long> taskIds = taskList.stream().map(Task::getId).collect(Collectors.toList());
        taskService.delete(new EntityWrapper<Task>().in("id",taskIds));
        taskService.delete(new EntityWrapper<Task>().in("id", taskIds));
        // 批量更新暂存点状态
        List<String> locOList = new ArrayList<>();
@@ -1134,20 +1023,22 @@
        log.info("agv任务档转历史成功:{}", taskIds);
    }
    @Transactional(rollbackFor = Exception.class)
    public void moveTaskToHistory(Task agvTask) {
        moveTaskToHistory(Collections.singletonList(agvTask));
    }
    /**
     * 货物到达出库口,生成agv任务
     */
    public void createAgvOutTasks(List<String> sites) {
        // 获取到可用出库站点的任务
        List<WrkMast> wrkMastList = wrkMastMapper.selectList(new EntityWrapper<WrkMast>().eq("call_agv", 1).in("sta_no",sites));
        List<WrkMast> wrkMastList = wrkMastMapper.selectList(new EntityWrapper<WrkMast>().eq("call_agv", 1).in("sta_no", sites));
        for(WrkMast wrkMast:wrkMastList) {
        for (WrkMast wrkMast : wrkMastList) {
            // todo 计算agv目标暂存位
            int endSite = 2004;
@@ -1159,12 +1050,13 @@
            wrkMastMapper.updateById(wrkMast);
            // 更新暂存位状态 S.入库预约
            basStationMapper.updateLocStsBatch( Collections.singletonList(String.valueOf(endSite)), "S");
            basStationMapper.updateLocStsBatch(Collections.singletonList(String.valueOf(endSite)), "S");
        }
    }
    /**
     * 取消AGV任务(仙工M4接口)
     *
     * @param task 任务对象
     * @return 是否成功
     */
@@ -1183,7 +1075,7 @@
        String url = ApiInterfaceConstant.AGV_IP + ApiInterfaceConstant.AGV_CANCEL_TASK_PATH;
        String namespace = "";
        String kind = "";
        // 根据任务类型确定kind和namespace
        switch (task.getIoType()) {
            case 1:
@@ -1224,7 +1116,7 @@
                    .setJson(body)
                    .build()
                    .doPost();
            JSONObject jsonObject = JSON.parseObject(response);
            if (jsonObject.getInteger("code") != null && jsonObject.getInteger("code").equals(200)) {
                success = true;
src/main/java/com/zy/common/constant/ApiInterfaceConstant.java
@@ -46,4 +46,11 @@
     */
    public static final String AGV_CANCEL_TASK_PATH = "/api/agv/cancelTransport";
    /* ***************************************Falcon接口对接 start*******************************************************/
    /**
     * agv - 查询订单状态
     */
    public static final String AGV_FIND_ONE_PATH = "/api/entity/find/one";
}
src/main/resources/mapper/TaskMapper.xml
@@ -34,13 +34,13 @@
    <select id="selectToBeCompleteData" resultMap="BaseResultMap">
        select * from agv_task where ((wrk_sts = 4 Or wrk_sts = 14 Or wrk_sts = 15 ) and io_type != 103 and io_type != 104 and io_type != 107 ) or (wrk_sts = 2 and io_type=6) order by upd_mk,error_time,io_time,wrk_no
        select * from agv_task where is_deleted = 0 and(((wrk_sts = 4 Or wrk_sts = 14 Or wrk_sts = 15 ) and io_type != 103 and io_type != 104 and io_type != 107 ) or (wrk_sts = 2 and io_type=6) ) order by upd_mk,error_time,io_time,wrk_no
    </select>
    <select id="selectToBeHistoryData" resultMap="BaseResultMap">
        select * from agv_task
        where wrk_sts=5 or wrk_sts=15
        where  is_deleted = 0 and( wrk_sts=5 or wrk_sts=15)
        order by io_time,wrk_no asc
    </select>
</mapper>