自动化立体仓库 - WMS系统
chen.llin
15 小时以前 73e42333948a8143c54218cd26435c2233daf279
agv出入库逻辑
1个文件已添加
19个文件已修改
563 ■■■■■ 已修改文件
src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/AgvScheduler.java 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/ErrorStockScheduler.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/InventoryReserveScheduler.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/NotifyLogScheduler.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/OrderMoveHistoryScheduler.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/OrderSyncScheduler.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/OverYearLogScheduler.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/PlcLogScheduler.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/WorkLogScheduler.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/WorkMastScheduler.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/handler/AgvHandler.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/handler/WorkMastHandler.java 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/properties/SchedulerProperties.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/timer/LicenseTimer.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/timer/LoadingConfigTimer.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/timer/TokenTimer.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-prod.yml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/TaskMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java
@@ -76,8 +76,14 @@
        // 根据taskId查询任务
        Task task = null;
        try {
            // 处理"T"前缀格式(如"T130"),这是AGV任务创建时的格式
            String numericId = taskId;
            if (taskId.startsWith("T") && taskId.length() > 1) {
                numericId = taskId.substring(1);
                log.debug("检测到T前缀格式的taskId,提取数字ID:{}", numericId);
            }
            // 尝试将taskId解析为Long类型的id
            Long taskIdLong = Long.parseLong(taskId);
            Long taskIdLong = Long.parseLong(numericId);
            task = taskService.selectById(taskIdLong);
        } catch (NumberFormatException e) {
            // 如果不是数字,尝试通过其他字段查询(如sheetNo等)
@@ -131,8 +137,16 @@
            String kind = param.getKind();
            if ("货物转运".equals(kind)) {
                log.info("处理货物转运任务,taskId:{}", taskId);
            } else if ("实托入库".equals(kind)) {
                log.info("处理实托入库任务,taskId:{}", taskId);
            } else if ("实托入库".equals(kind) || "空托入库".equals(kind)) {
                log.info("处理入库任务,taskId:{}", taskId);
                // 入库任务:如果收到确认取货回调(loaded=true),完结AGV呼叫单
                if (Boolean.TRUE.equals(param.getLoaded())) {
                    // 如果任务状态是8(已呼叫AGV),更新为9(任务完成)
                    if (task.getWrkSts() != null && task.getWrkSts() == 8L) {
                        task.setWrkSts(9L);
                        log.info("入库任务收到确认取货回调,完结AGV呼叫单,taskId:{}", taskId);
                    }
                }
            } else if ("实托出库".equals(kind)) {
                log.info("处理实托出库任务,taskId:{}", taskId);
            }
src/main/java/com/zy/asrs/task/AgvScheduler.java
@@ -1,21 +1,27 @@
package com.zy.asrs.task;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.core.common.Cools;
import com.zy.asrs.entity.Task;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.mapper.WrkMastMapper;
import com.zy.asrs.entity.WrkMastLog;
import com.zy.asrs.service.TaskService;
import com.zy.asrs.service.WrkMastLogService;
import com.zy.asrs.service.WrkMastService;
import com.zy.asrs.task.handler.AgvHandler;
import com.zy.common.properties.SchedulerProperties;
import com.zy.system.entity.Config;
import com.zy.system.service.ConfigService;
import org.springframework.scheduling.TaskScheduler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
@@ -23,6 +29,7 @@
 * @description AGV交互相关定时任务
 * @createDate 2025/11/18 14:18
 */
@Slf4j
@Component
public class AgvScheduler {
@@ -38,13 +45,29 @@
    @Resource
    private WrkMastMapper wrkMastMapper;
    @Resource
    private WrkMastService wrkMastService;
    @Resource
    private WrkMastLogService wrkMastLogService;
    @Resource
    private SchedulerProperties schedulerProperties;
    /**
     * 呼叫agv搬运
     */
    @Scheduled(cron = "0/5 * * * * ? ")
    private void callAgv() {
        // 查询待呼叫agv任务
        List<Task> taskList = taskService.selectList(new EntityWrapper<Task>().eq("wrk_sts", 7));
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        // 查询待呼叫agv任务,按id升序排序(id最小的优先呼叫)
        List<Task> taskList = taskService.selectList(
            new EntityWrapper<Task>()
                .eq("wrk_sts", 7)
                .orderBy("id", true) // 按id升序,id最小的优先
        );
        if(taskList.isEmpty()) {
            return;
        }
@@ -56,6 +79,9 @@
     */
    @Scheduled(cron = "0/3 * * * * ? ")
    private void createAgvOutTasks() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        // 获取呼叫agv配置
        List<Config> configs = configService.selectList(new EntityWrapper<Config>().in("code", "eastCallAgvControl", "westCallAgvControl").eq("status", 1));
@@ -86,6 +112,9 @@
     */
    @Scheduled(cron = "0/10 * * * * ? ")
    private void moveTaskToHistory() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        List<Task> taskList = taskService.selectList(new EntityWrapper<Task>().eq("wrk_sts", 9));
        if(taskList.isEmpty()) {
            return;
@@ -93,5 +122,191 @@
        agvHandler.moveTaskToHistory(taskList);
    }
    /**
     * 检查入库成功的任务,完结对应的AGV呼叫单
     * 如果入库任务呼叫AGV后没有收到回调,但工作档已经入库成功,则完结AGV呼叫单
     */
    @Scheduled(cron = "0/10 * * * * ? ")
    private void checkInboundCompletedTasks() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        try {
            // 查询入库成功的工作档(状态4:入库完成,入库类型:1,10,53,57)
            List<WrkMast> completedWrkMasts = wrkMastService.selectList(
                new EntityWrapper<WrkMast>()
                    .eq("wrk_sts", 4L)  // 入库完成
                    .in("io_type", 1, 10, 53, 57)  // 入库类型
                    .isNotNull("wrk_no")
            );
            if (completedWrkMasts.isEmpty()) {
                return;
            }
            Date now = new Date();
            int completedCount = 0;
            for (WrkMast wrkMast : completedWrkMasts) {
                // 查找对应的AGV任务(优先通过wrk_no查询)
                Wrapper<Task> taskWrapper1 = new EntityWrapper<Task>()
                    .eq("task_type", "agv")
                    .eq("wrk_sts", 8L)  // 已呼叫AGV状态
                    .eq("wrk_no", wrkMast.getWrkNo());
                List<Task> agvTasks = taskService.selectList(taskWrapper1);
                // 如果通过wrk_no没找到,且有条码,则通过条码查询
                if (agvTasks.isEmpty() && !Cools.isEmpty(wrkMast.getBarcode())) {
                    Wrapper<Task> taskWrapper2 = new EntityWrapper<Task>()
                        .eq("task_type", "agv")
                        .eq("wrk_sts", 8L)
                        .eq("barcode", wrkMast.getBarcode());
                    agvTasks = taskService.selectList(taskWrapper2);
                }
                for (Task agvTask : agvTasks) {
                    // 确保是入库任务
                    if (agvTask.getIoType() != null &&
                        (agvTask.getIoType() == 1 || agvTask.getIoType() == 10 ||
                         agvTask.getIoType() == 53 || agvTask.getIoType() == 57)) {
                        // 更新AGV任务状态为完成
                        agvTask.setWrkSts(9L);
                        agvTask.setModiTime(now);
                        if (taskService.updateById(agvTask)) {
                            completedCount++;
                            log.info("入库任务工作档已入库成功,完结AGV呼叫单,taskId:{},wrkNo:{},barcode:{}",
                                agvTask.getId(), wrkMast.getWrkNo(), wrkMast.getBarcode());
                        }
                    }
                }
            }
            if (completedCount > 0) {
                log.info("本次检查完结了{}个入库AGV呼叫单", completedCount);
            }
        } catch (Exception e) {
            log.error("检查入库成功任务并完结AGV呼叫单异常", e);
        }
    }
    /**
     * 检查AGV任务对应的工作档是否已完成或已转历史档并完结
     * 处理被跳过的AGV任务:如果工作档已完成(wrk_sts=4,5,14,15)或已转历史档并完结,则完结AGV任务
     */
    @Scheduled(cron = "0/10 * * * * ? ")
    private void checkCompletedTasksInHistory() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        try {
            // 查询状态为8(已呼叫AGV)的AGV任务
            List<Task> agvTasks = taskService.selectList(
                new EntityWrapper<Task>()
                    .eq("task_type", "agv")
                    .eq("wrk_sts", 8L)  // 已呼叫AGV状态
                    .isNotNull("wrk_no")
            );
            if (agvTasks.isEmpty()) {
                return;
            }
            Date now = new Date();
            int completedCount = 0;
            for (Task agvTask : agvTasks) {
                boolean isCompleted = false;
                String reason = "";
                // 检查工作档是否存在
                WrkMast wrkMast = null;
                if (agvTask.getWrkNo() != null) {
                    wrkMast = wrkMastService.selectOne(
                        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(库存更新完成)
                        if ((ioType == 1 || ioType == 10 || ioType == 53 || ioType == 57) &&
                            (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)) {
                            isCompleted = true;
                            reason = String.format("工作档已完成,状态:%d", wrkSts);
                        }
                    }
                } else {
                    // 如果工作档不存在,检查历史档
                    WrkMastLog wrkMastLog = null;
                    // 优先通过wrk_no查询历史档
                    if (agvTask.getWrkNo() != null) {
                        wrkMastLog = wrkMastLogService.selectOne(
                            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())
                        );
                        if (!logList.isEmpty()) {
                            wrkMastLog = logList.get(0); // 取第一个
                        }
                    }
                    // 如果历史档存在且已完结,则完结AGV任务
                    if (wrkMastLog != null) {
                        Integer ioType = agvTask.getIoType();
                        long logWrkSts = wrkMastLog.getWrkSts();
                        if (ioType != null) {
                            // 入库任务:状态5(库存更新完成)
                            if ((ioType == 1 || ioType == 10 || ioType == 53 || ioType == 57) &&
                                logWrkSts == 5L) {
                                isCompleted = true;
                                reason = String.format("工作档已转历史档并完结,历史档状态:%d", logWrkSts);
                            }
                            // 出库任务:状态15(出库更新完成)
                            else if ((ioType == 101 || ioType == 110 || ioType == 103 || ioType == 107) &&
                                     logWrkSts == 15L) {
                                isCompleted = true;
                                reason = String.format("工作档已转历史档并完结,历史档状态:%d", logWrkSts);
                            }
                        }
                    }
                }
                // 如果已完成,更新AGV任务状态
                if (isCompleted) {
                    agvTask.setWrkSts(9L);
                    agvTask.setModiTime(now);
                    if (taskService.updateById(agvTask)) {
                        completedCount++;
                        log.info("{},完结AGV呼叫单,taskId:{},wrkNo:{},barcode:{},站点:{}",
                            reason, agvTask.getId(), agvTask.getWrkNo(), agvTask.getBarcode(), agvTask.getStaNo());
                    }
                }
            }
            if (completedCount > 0) {
                log.info("本次检查完结了{}个AGV呼叫单(工作档已完成或已转历史档)", completedCount);
            }
        } catch (Exception e) {
            log.error("检查工作档已完成或历史档完结任务并完结AGV呼叫单异常", e);
        }
    }
}
src/main/java/com/zy/asrs/task/ErrorStockScheduler.java
@@ -2,11 +2,14 @@
import com.zy.asrs.task.core.ReturnT;
import com.zy.asrs.task.handler.ErrorStockHandler;
import com.zy.common.properties.SchedulerProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
 * Created by vincent on 2020/7/7
@@ -19,8 +22,14 @@
    @Autowired
    private ErrorStockHandler errorStockHandler;
    @Resource
    private SchedulerProperties schedulerProperties;
    @Scheduled(cron = "0/3 * * * * ? ")
    private void execute() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        ReturnT<String> returnT = errorStockHandler.start();
        if (!returnT.isSuccess()) {
            log.error(returnT.getMsg());
src/main/java/com/zy/asrs/task/InventoryReserveScheduler.java
@@ -2,10 +2,13 @@
import com.zy.asrs.task.core.ReturnT;
import com.zy.asrs.task.handler.InventoryReserveExpireHandler;
import com.zy.common.properties.SchedulerProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
 * 预留库存过期定时任务调度器
@@ -18,11 +21,17 @@
    @Autowired
    private InventoryReserveExpireHandler inventoryReserveExpireHandler;
    @Resource
    private SchedulerProperties schedulerProperties;
    /**
     * 每分钟执行一次,检查过期的预留库存
     */
    @Scheduled(cron = "0 * * * * ?")
    public void checkExpiredReserve() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        try {
            ReturnT<String> result = inventoryReserveExpireHandler.start();
            if (!result.isSuccess()) {
src/main/java/com/zy/asrs/task/NotifyLogScheduler.java
@@ -2,11 +2,14 @@
import com.zy.asrs.task.core.ReturnT;
import com.zy.asrs.task.handler.NotifyLogHandler;
import com.zy.common.properties.SchedulerProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
 * Created by vincent on 2020/7/7
@@ -19,8 +22,14 @@
    @Autowired
    private NotifyLogHandler notifyLogHandler;
    @Resource
    private SchedulerProperties schedulerProperties;
    @Scheduled(cron = "0/3 * * * * ? ")
    private void execute() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        ReturnT<String> returnT = notifyLogHandler.start();
        if (!returnT.isSuccess()) {
            log.error(returnT.getMsg());
src/main/java/com/zy/asrs/task/OrderMoveHistoryScheduler.java
@@ -2,9 +2,12 @@
import com.zy.asrs.task.handler.OrderPakinMoveHistoryHandler;
import com.zy.asrs.task.handler.OrderPakoutMoveHistoryHandler;
import com.zy.common.properties.SchedulerProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
@@ -15,11 +18,17 @@
    @Autowired
    private OrderPakoutMoveHistoryHandler orderPakoutMoveHistoryHandler;
    @Resource
    private SchedulerProperties schedulerProperties;
    /**
     * 将已完成order和orderDetl移动到log表
     */
    @Scheduled(cron = "0/30 * * * * ?")
    public void execute() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        orderPakinMoveHistoryHandler.start();
    }
@@ -28,6 +37,9 @@
     */
    @Scheduled(cron = "0/30 * * * * ?")
    public void executeOrder() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        orderPakoutMoveHistoryHandler.start();
    }
}
src/main/java/com/zy/asrs/task/OrderSyncScheduler.java
@@ -8,6 +8,7 @@
import com.zy.asrs.task.core.ReturnT;
import com.zy.asrs.task.handler.OrderPakinSyncHandler;
import com.zy.asrs.task.handler.OrderPakoutSyncHandler;
import com.zy.common.properties.SchedulerProperties;
import com.zy.system.timer.LoadingConfigTimer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -15,6 +16,7 @@
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
@@ -37,9 +39,15 @@
    @Autowired
    private LoadingConfigTimer loadingConfigTimer;
    @Resource
    private SchedulerProperties schedulerProperties;
    @Scheduled(cron = "0/30 * * * * ? ")
    @Async("orderThreadPool")
    public void completeOrderPakin() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        if (loadingConfigTimer.getErpReport()) {
            List<OrderPakin> orders = orderPakinService.selectComplete();
            for (OrderPakin order : orders) {
@@ -59,6 +67,9 @@
    @Scheduled(cron = "0/30 * * * * ? ")
    @Async("orderThreadPool")
    public void completeOrderPakout() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        if (loadingConfigTimer.getErpReport()) {
            List<OrderPakout> orders = orderPakoutService.selectComplete();
            for (OrderPakout order : orders) {
@@ -77,6 +88,9 @@
    @Scheduled(cron = "0 0 1 * * ? ")
    public void clearApiLog() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        try {
            apiLogService.clearWeekBefore();
        } catch (Exception e) {
src/main/java/com/zy/asrs/task/OverYearLogScheduler.java
@@ -2,11 +2,14 @@
import com.zy.asrs.task.core.ReturnT;
import com.zy.asrs.task.handler.OverYearLogHandler;
import com.zy.common.properties.SchedulerProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
 * Created by vincent on 2020/7/7
@@ -19,8 +22,14 @@
    @Autowired
    private OverYearLogHandler overYearLogHandler;
    @Resource
    private SchedulerProperties schedulerProperties;
    @Scheduled(cron = "0 0 1 * * ? ")
    private void execute() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        ReturnT<String> returnT = overYearLogHandler.start();
        if (!returnT.isSuccess()) {
            log.error(returnT.getMsg());
src/main/java/com/zy/asrs/task/PlcLogScheduler.java
@@ -2,11 +2,14 @@
import com.zy.asrs.task.core.ReturnT;
import com.zy.asrs.task.handler.PlcLogHandler;
import com.zy.common.properties.SchedulerProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
 * Created by vincent on 2020/7/7
@@ -19,8 +22,14 @@
    @Autowired
    private PlcLogHandler plcLogHandler;
    @Resource
    private SchedulerProperties schedulerProperties;
    @Scheduled(cron = "0/3 * * * * ? ")
    private void execute() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        ReturnT<String> returnT = plcLogHandler.start();
        if (!returnT.isSuccess()) {
            log.error(returnT.getMsg());
src/main/java/com/zy/asrs/task/WorkLogScheduler.java
@@ -6,11 +6,13 @@
import com.zy.asrs.service.WrkMastService;
import com.zy.asrs.task.core.ReturnT;
import com.zy.asrs.task.handler.WorkLogHandler;
import com.zy.common.properties.SchedulerProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
@@ -29,9 +31,14 @@
    @Autowired
    private TaskService taskService;
    @Resource
    private SchedulerProperties schedulerProperties;
    @Scheduled(cron = "0/3 * * * * ? ")
    private void execute() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        List<WrkMast> wrkMasts = wrkMastService.selectToBeHistoryData();
        if (wrkMasts.isEmpty()) {
            return;
@@ -47,6 +54,9 @@
    @Scheduled(cron = "0/3 * * * * ? ")
    private void executeTask(){
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        List<Task> taskList = taskService.selectToBeHistoryData();
        if (taskList.isEmpty()) {
            return;
src/main/java/com/zy/asrs/task/WorkMastScheduler.java
@@ -6,12 +6,14 @@
import com.zy.asrs.service.WrkMastService;
import com.zy.asrs.task.core.ReturnT;
import com.zy.asrs.task.handler.WorkMastHandler;
import com.zy.common.properties.SchedulerProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
@@ -30,8 +32,14 @@
    @Autowired
    private TaskService taskService;
    @Resource
    private SchedulerProperties schedulerProperties;
    @Scheduled(cron = "0/3 * * * * ? ")
    private void execute() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        List<WrkMast> wrkMasts = wrkMastService.selectToBeCompleteData();
        if (wrkMasts.isEmpty()) {
            return;
@@ -51,6 +59,9 @@
    @Scheduled(cron = "0/3 * * * * ? ")
    private void executeTask() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        List<Task> wrkMasts = taskService.selectToBeCompleteData();
        if (wrkMasts.isEmpty()) {
            return;
src/main/java/com/zy/asrs/task/handler/AgvHandler.java
@@ -87,19 +87,38 @@
                log.info("任务ID:{}已分配站点:{}", task.getId(), staNo);
            }
            
            // 检查目标站点是否有未完成的AGV任务
            // 如果站点有未完成的任务,则跳过本次发送,等待下次
            if (staNo != null && !staNo.isEmpty()) {
                List<Task> unfinishedTasks = taskService.selectList(new EntityWrapper<Task>()
            // 检查目标站点是否有正在搬运的同类型AGV任务(出库和入库互不干扰)
            // 只有状态8(已呼叫AGV,正在搬运)的任务才会阻塞,状态7(待呼叫)的任务不阻塞
            // 这样可以避免所有任务都卡在呼叫状态,按id最小的优先呼叫
            if (staNo != null && !staNo.isEmpty() && task.getIoType() != null) {
                // 根据当前任务类型,只检查同类型的正在搬运任务(状态8)
                // 入库任务(ioType < 100):只检查入库类型的正在搬运任务
                // 出库任务(ioType >= 100):只检查出库类型的正在搬运任务
                List<Integer> ioTypes;
                String taskType;
                if (task.getIoType() < 100) {
                    // 入库任务:只检查入库类型(1, 10, 53, 57)
                    ioTypes = Arrays.asList(1, 10, 53, 57);
                    taskType = "入库";
                } else {
                    // 出库任务:只检查出库类型(101, 110, 103, 107)
                    ioTypes = Arrays.asList(101, 110, 103, 107);
                    taskType = "出库";
                }
                // 只检查状态为8(已呼叫AGV,正在搬运)的同类型任务
                List<Task> transportingTasks = taskService.selectList(
                    new EntityWrapper<Task>()
                        .eq("sta_no", staNo)
                        .eq("task_type", "agv")
                        .eq("wrk_sts", 8L) // 只检查正在搬运状态的任务
                        .in("io_type", ioTypes)
                        .ne("id", task.getId()) // 排除当前任务本身
                        .last("AND wrk_sts NOT IN (5, 15)") // 排除已完成状态
                );
                
                if (!unfinishedTasks.isEmpty()) {
                    log.info("站点{}有{}个未完成的AGV任务,跳过本次发送,等待任务完成。当前任务ID:{}",
                            staNo, unfinishedTasks.size(), task.getId());
                if (!transportingTasks.isEmpty()) {
                    log.info("站点{}有{}个正在搬运的{}AGV任务,跳过本次发送,等待搬运完成。当前任务ID:{}",
                            staNo, transportingTasks.size(), taskType, task.getId());
                    continue; // 跳过本次发送,等待下次
                }
            }
src/main/java/com/zy/asrs/task/handler/WorkMastHandler.java
@@ -9,6 +9,9 @@
import com.zy.asrs.service.impl.BasStationServiceImpl;
import com.zy.asrs.task.AbstractHandler;
import com.zy.asrs.task.core.ReturnT;
import com.zy.common.model.enums.WorkNoType;
import com.zy.common.properties.AgvProperties;
import com.zy.common.service.CommonService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -16,6 +19,7 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@@ -59,6 +63,12 @@
    @Autowired
    private LocCacheDetlService locCacheDetlService;
    @Autowired
    private CommonService commonService;
    @Autowired
    private AgvProperties agvProperties;
    public ReturnT<String> start(WrkMast wrkMast) {
        // 4.入库完成
@@ -590,6 +600,9 @@
            // 14.出库完成
        } else if (task.getWrkSts() == 14) {
            return agvDoOut(task);
            // 15.出库更新完成 - 生成空托/满托出库任务
        } else if (task.getWrkSts() == 15) {
            return generateEmptyOrFullPalletOutTaskForCompleted(task);
        }
        return SUCCESS;
    }
@@ -636,6 +649,7 @@
            if (!taskService.updateById(task)) {
                throw new CoolException("任务状态修改失败!!");
            }
            // 注意:生成空托/满托出库任务的逻辑已移至状态15的处理方法中
        } else if(task.getIoType().equals(53) || task.getIoType().equals(54) || task.getIoType().equals(57)){
            LocCache locCache = locCacheService.selectOne(new EntityWrapper<LocCache>().eq("loc_no", task.getSourceLocNo()));
            if (Objects.isNull(locCache)) {
@@ -669,6 +683,128 @@
        return SUCCESS;
    }
    /**
     * 状态15(出库更新完成)时,生成空托出库或满托出库任务,将托盘放入缓存库位
     * @param completedTask 已完成出库更新的任务(状态15)
     * @return 处理结果
     */
    @Transactional(rollbackFor = Exception.class)
    public ReturnT<String> generateEmptyOrFullPalletOutTaskForCompleted(Task completedTask) {
        // 只处理ioType=101的全板出库任务
        if (!completedTask.getIoType().equals(101)) {
            return SUCCESS;
        }
        // 检查是否已经生成过空托/满托出库任务(避免重复生成)
        List<Task> existingTasks = taskService.selectList(new EntityWrapper<Task>()
                .eq("barcode", completedTask.getBarcode())
                .in("io_type", 110, 101) // 空板出库或全板出库
                .eq("wrk_sts", 7) // 待呼叫AGV状态
        );
        if (!existingTasks.isEmpty()) {
            log.info("任务ID:{}的托盘码:{}已存在空托/满托出库任务,跳过生成", completedTask.getId(), completedTask.getBarcode());
            return SUCCESS;
        }
        try {
            generateEmptyOrFullPalletOutTask(completedTask, null);
            return SUCCESS;
        } catch (Exception e) {
            log.error("状态15时生成空托/满托出库任务失败,任务ID:{},错误:{}", completedTask.getId(), e.getMessage(), e);
            return FAIL.setMsg("生成空托/满托出库任务失败:" + e.getMessage());
        }
    }
    /**
     * 出库完成后,生成空托出库或满托出库任务,将托盘放入缓存库位
     * @param outTask 出库任务
     * @param sourceLocCache 源库位(可为null)
     */
    private void generateEmptyOrFullPalletOutTask(Task outTask, LocCache sourceLocCache) {
        // 判断托盘类型:空托或满托
        boolean isEmptyPallet = "Y".equals(outTask.getEmptyMk());
        Integer ioType = isEmptyPallet ? 110 : 101; // 110=空板出库,101=全板出库(满托出库)
        log.info("出库任务完成,生成{}任务,任务ID:{},托盘码:{}", isEmptyPallet ? "空托出库" : "满托出库", outTask.getId(), outTask.getBarcode());
        // 分配缓存库位(whs_type=2)
        LocCache cacheLoc = locCacheService.selectOne(new EntityWrapper<LocCache>()
                .eq("whs_type", agvProperties.getWhsTypeMapping().getCacheArea()) // whs_type=2 缓存区
                .eq("frozen", 0)
                .eq("loc_sts", LocStsType.LOC_STS_TYPE_O.type) // O.闲置
                .ne("full_plt", isEmptyPallet ? "Y" : "N") // 空托不选满板库位,满托不选空板库位
                .orderAsc(Arrays.asList("row1", "bay1", "lev1"))
                .last("OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY"));
        if (cacheLoc == null) {
            log.warn("没有可用的缓存库位,无法生成{}任务,任务ID:{}", isEmptyPallet ? "空托出库" : "满托出库", outTask.getId());
            return;
        }
        // 获取出库站点(出库任务的staNo是出库站点,将作为空托/满托出库任务的源站点)
        String outboundStaNo = outTask.getStaNo();
        if (outboundStaNo == null || outboundStaNo.isEmpty()) {
            log.warn("出库任务没有出库站点,无法生成{}任务,任务ID:{}", isEmptyPallet ? "空托出库" : "满托出库", outTask.getId());
            return;
        }
        // 根据缓存区配置选择站点和机器人组(西侧)
        List<String> cacheStations = agvProperties.getWestStations();
        String robotGroup = agvProperties.getRobotGroupWest();
        if (cacheStations.isEmpty()) {
            log.warn("缓存区没有配置站点,无法生成{}任务,任务ID:{}", isEmptyPallet ? "空托出库" : "满托出库", outTask.getId());
            return;
        }
        // 选择缓存区目标站点(使用第一个可用站点,或可以优化为选择任务最少的站点)
        String cacheStaNo = cacheStations.get(0);
        // 生成工作号
        int workNo = commonService.getWorkNo(WorkNoType.PAKOUT.type);
        // 创建空托出库/满托出库任务
        Task cacheTask = new Task();
        Date now = new Date();
        cacheTask.setWrkNo(workNo)
                .setIoTime(now)
                .setWrkSts(7L) // 工作状态:7.待呼叫AGV
                .setIoType(ioType) // 110=空板出库,101=全板出库
                .setTaskType("agv")
                .setIoPri(10D)
                .setStaNo(cacheStaNo) // 目标站点(缓存区站点)
                .setSourceStaNo(outboundStaNo) // 源站点(出库站点)
                .setInvWh(robotGroup) // 机器人组(西侧)
                .setFullPlt(isEmptyPallet ? "N" : "Y") // 满板:空托=N,满托=Y
                .setPicking("N")
                .setExitMk("N")
                .setSourceLocNo(null) // 出库任务不需要源库位
                .setLocNo(cacheLoc.getLocNo()) // 目标库位(缓存库位)
                .setEmptyMk(isEmptyPallet ? "Y" : "N") // 空板标记
                .setBarcode(outTask.getBarcode()) // 托盘码
                .setLinkMis("N")
                .setAppeTime(now)
                .setModiTime(now);
        if (!taskService.insert(cacheTask)) {
            log.error("生成{}任务失败,任务ID:{}", isEmptyPallet ? "空托出库" : "满托出库", outTask.getId());
            return;
        }
        // 更新缓存库位状态:O.闲置 → S.入库预约
        cacheLoc.setLocSts(LocStsType.LOC_STS_TYPE_S.type);
        cacheLoc.setBarcode(outTask.getBarcode());
        cacheLoc.setModiTime(now);
        if (!locCacheService.updateById(cacheLoc)) {
            log.error("更新缓存库位状态失败,库位:{}", cacheLoc.getLocNo());
            // 回滚任务
            taskService.deleteById(cacheTask.getId());
            return;
        }
        log.info("成功生成{}任务,任务ID:{},工作号:{},源站点:{},目标站点:{},缓存库位:{}",
                isEmptyPallet ? "空托出库" : "满托出库", cacheTask.getId(), workNo, outboundStaNo, cacheStaNo, cacheLoc.getLocNo());
    }
    @Transactional(rollbackFor = Exception.class)
    public ReturnT<String> agvDoIn(Task wrkMast) {
src/main/java/com/zy/common/properties/SchedulerProperties.java
New file
@@ -0,0 +1,23 @@
package com.zy.common.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
 * 定时任务配置属性
 *
 * @author system
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "scheduler")
public class SchedulerProperties {
    /**
     * 定时任务总开关,false时所有定时任务都不会启动
     * 默认值为true,保持向后兼容
     */
    private boolean enabled = true;
}
src/main/java/com/zy/system/timer/LicenseTimer.java
@@ -2,6 +2,7 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zy.common.properties.SchedulerProperties;
import com.zy.common.utils.HttpHandler;
import com.zy.system.entity.LicenseInfos;
import com.zy.system.entity.license.*;
@@ -12,6 +13,7 @@
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
@@ -50,9 +52,15 @@
    @Autowired
    private LicenseInfosService licenseInfosService;
    @Resource
    private SchedulerProperties schedulerProperties;
    //每天晚上11点更新系统激活状态
    @Scheduled(cron = "0 0 23 * * ? ")
    public void timer() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        try {
            getRemoteLicense();
        } catch (Exception e) {
src/main/java/com/zy/system/timer/LoadingConfigTimer.java
@@ -1,11 +1,14 @@
package com.zy.system.timer;
import com.zy.common.properties.SchedulerProperties;
import com.zy.system.service.ConfigService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
 * 定时任务读取配置信息
@@ -39,8 +42,14 @@
    @Autowired
    private ConfigService configService;
    @Resource
    private SchedulerProperties schedulerProperties;
    @Scheduled(cron = "0/2 * * * * ? ")
    public void timer() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        tokenExpire = configService.getVal("tokenExpire", Integer.class, tokenExpire);
        tokenNumber = configService.getVal("tokenNumber", Integer.class, tokenNumber);
        logDeleteDays = configService.getVal("logDeleteDays", Integer.class, logDeleteDays);
src/main/java/com/zy/system/timer/TokenTimer.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.Cools;
import com.zy.common.properties.SchedulerProperties;
import com.zy.system.entity.UserLogin;
import com.zy.system.service.UserLoginService;
import lombok.extern.slf4j.Slf4j;
@@ -10,6 +11,7 @@
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
@@ -28,8 +30,14 @@
    @Value("${super.pwd}")
    private String superPwd;
    @Resource
    private SchedulerProperties schedulerProperties;
    @Scheduled(cron = "0/30 * * * * ? ")
    public void timer() {
        if (!schedulerProperties.isEnabled()) {
            return;
        }
        if (loadingConfigTimer.getTokenNumber() == 1) {
            return;
        }
src/main/resources/application-dev.yml
@@ -106,4 +106,8 @@
  # 虚拟库位编号
  virtual-location-no: VIRTUAL
# 定时任务配置
scheduler:
  # 定时任务总开关,false时所有定时任务都不会启动
  enabled: true
src/main/resources/application-prod.yml
@@ -105,3 +105,8 @@
  outbound-doc-type-id: 36
  # 虚拟库位编号
  virtual-location-no: VIRTUAL
# 定时任务配置
scheduler:
  # 定时任务总开关,false时所有定时任务都不会启动
  enabled: true
src/main/resources/mapper/TaskMapper.xml
@@ -34,7 +34,7 @@
    <select id="selectToBeCompleteData" resultMap="BaseResultMap">
        select * from agv_task where ((wrk_sts = 4 Or wrk_sts = 14 ) 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 ((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>