自动化立体仓库 - WMS系统
chen.llin
3 天以前 65539f6fb4d836180f784c2c4e0bc441764cd23f
agv逻辑调整
10个文件已修改
365 ■■■■ 已修改文件
src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/AgvScheduler.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/handler/AgvHandler.java 80 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/handler/WorkMastHandler.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/controller/UserController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/User.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/logback-spring.xml 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/UserMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/user/user.js 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/user/user.html 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/OpenServiceImpl.java
@@ -118,6 +118,11 @@
        if (!Cools.isEmpty(param.getStatus())) {
            switch (param.getStatus()) {
                case "Assigned":
                    // AGV已派车,确认接收命令,记录确认时间
                    if (task.getPlcStrTime() == null) {
                        task.setPlcStrTime(new Date());
//                        log.info("AGV已确认接收命令,记录确认时间,taskId:{}", taskId);
                    }
                    if (Cools.isEmpty(robotGroup)) {
                        task.setInvWh(param.getRobotName());
                    }
src/main/java/com/zy/asrs/task/AgvScheduler.java
@@ -203,7 +203,10 @@
    /**
     * 检查AGV任务对应的工作档是否已完成或已转历史档并完结
     * 处理被跳过的AGV任务:如果工作档已完成(wrk_sts=4,5,14,15)或已转历史档并完结,则完结AGV任务
     * 处理被跳过的AGV任务:
     * 1. 如果工作档已完成(wrk_sts=4,5,14,15)或已转历史档并完结,则完结AGV任务
     * 2. 如果工作档和历史档都没有数据,但是AGV已确认接收命令后超过1小时处于搬运中(状态8),也结束AGV任务
     *    注意:只有AGV确认接收命令后(plcStrTime不为空)才开始计时,如果AGV接受命令失败,会继续呼叫AGV
     */
    @Scheduled(cron = "0/10 * * * * ? ")
    private void checkCompletedTasksInHistory() {
@@ -216,7 +219,6 @@
                new EntityWrapper<Task>()
                    .eq("task_type", "agv")
                    .eq("wrk_sts", 8L)  // 已呼叫AGV状态
                    .isNotNull("wrk_no")
            );
            
            if (agvTasks.isEmpty()) {
@@ -226,6 +228,8 @@
            Date now = new Date();
            int completedCount = 0;
            List<Task> completedTasks = new ArrayList<>();
            // 1小时的毫秒数(从AGV确认接收命令开始计时)
            long oneHourInMillis = 60 * 60 * 1000L;
            
            for (Task agvTask : agvTasks) {
                boolean isCompleted = false;
@@ -237,6 +241,24 @@
                    wrkMast = wrkMastService.selectOne(
                        new EntityWrapper<WrkMast>().eq("wrk_no", agvTask.getWrkNo())
                    );
                }
                // 检查历史档是否存在(无论工作档是否存在都需要检查)
                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); // 取第一个
                    }
                }
                
                // 如果工作档存在,检查是否已完成
@@ -258,29 +280,10 @@
                            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) {
                // 如果工作档不存在或未完成,检查历史档是否已完结
                if (!isCompleted && wrkMastLog != null) {
                        Integer ioType = agvTask.getIoType();
                        long logWrkSts = wrkMastLog.getWrkSts();
                        
@@ -299,6 +302,23 @@
                            }
                        }
                    }
                // 如果工作档和历史档都没有数据,检查是否超过1小时(从AGV确认接收命令开始计时)
                if (!isCompleted && wrkMast == null && wrkMastLog == null) {
                    // 只有AGV确认接收命令后(plcStrTime不为空)才开始计时
                    // 如果plcStrTime为空,说明AGV还没有确认接收命令,会继续呼叫,不结束任务
                    Date agvConfirmedTime = agvTask.getPlcStrTime();
                    if (agvConfirmedTime != null) {
                        long timeDiff = now.getTime() - agvConfirmedTime.getTime();
                        if (timeDiff >= oneHourInMillis) {
                            isCompleted = true;
                            long hours = timeDiff / (60 * 60 * 1000L);
                            long minutes = (timeDiff % (60 * 60 * 1000L)) / (60 * 1000L);
                            reason = String.format("工作档和历史档都不存在,AGV确认接收命令后超过1小时(实际:%d小时%d分钟)处于搬运中,自动结束", hours, minutes);
                        }
                    }
                    // 如果plcStrTime为空,说明AGV还没有确认接收命令,不结束任务,继续等待呼叫
                }
                
                // 如果已完成,更新AGV任务状态并收集到列表
@@ -325,7 +345,7 @@
            }
            
            if (completedCount > 0) {
                log.info("本次检查完结了{}个AGV呼叫单(工作档已完成或已转历史档)", completedCount);
                log.info("本次检查完结了{}个AGV呼叫单(工作档已完成或已转历史档或确认接收后超时)", completedCount);
            }
        } catch (Exception e) {
            log.error("检查工作档已完成或历史档完结任务并完结AGV呼叫单异常", e);
src/main/java/com/zy/asrs/task/handler/AgvHandler.java
@@ -80,7 +80,7 @@
        for (Task task : taskList) {
            // 如果任务状态已经是8(已呼叫AGV,正在搬运),则不再发送指令
            if (task.getWrkSts() != null && task.getWrkSts() == 8L) {
                log.debug("任务ID:{}状态已是8(正在搬运),跳过发送", task.getId());
                // log.debug("任务ID:{}状态已是8(正在搬运),跳过发送", task.getId());
                continue;
            }
            
@@ -90,7 +90,7 @@
                String errorMsg = allocateSiteForTask(task);
                if (errorMsg != null) {
                    // 无法分配站点,只在日志中记录,不记录到任务中(app不需要知道)
                    log.warn("任务ID:{}无法分配站点:{}", task.getId(), errorMsg);
                    // log.warn("任务ID:{}无法分配站点:{}", task.getId(), errorMsg);
                    continue;
                }
                // 检查是否成功分配了站点(如果返回null且staNo仍为空,说明所有站点都在搬运,等待下次再试)
@@ -99,7 +99,7 @@
                    // 所有站点都在搬运,暂不分配,等待下次定时任务再尝试
                    continue;
                }
                log.info("任务ID:{}已分配站点:{}", task.getId(), staNo);
                // log.info("任务ID:{}已分配站点:{}", task.getId(), staNo);
            }
            
            // 检查目标站点是否有效(不为0且存在)
@@ -108,18 +108,18 @@
                    Integer siteNo = Integer.parseInt(staNo);
                    // 检查站点是否为0(无效站点),如果是0则不发送,但不清空站点
                    if (siteNo == null || siteNo == 0) {
                        log.warn("任务ID:{}的目标站点{}无效(为0),跳过发送,保留站点分配", task.getId(), staNo);
                        // log.warn("任务ID:{}的目标站点{}无效(为0),跳过发送,保留站点分配", task.getId(), staNo);
                        continue;
                    }
                    List<BasDevp> basDevpList = basDevpMapper.selectList(new EntityWrapper<BasDevp>().eq("dev_no", siteNo));
                    if (basDevpList == null || basDevpList.isEmpty()) {
                        // 站点不存在,跳过发送,不清空站点
                        log.warn("任务ID:{}的目标站点{}不存在,跳过发送,保留站点分配", task.getId(), staNo);
                        // log.warn("任务ID:{}的目标站点{}不存在,跳过发送,保留站点分配", task.getId(), staNo);
                        continue;
                    }
                } catch (NumberFormatException e) {
                    // 站点格式错误,跳过发送,不清空站点
                    log.warn("任务ID:{}的目标站点{}格式错误,跳过发送,保留站点分配", task.getId(), staNo);
                    // log.warn("任务ID:{}的目标站点{}格式错误,跳过发送,保留站点分配", task.getId(), staNo);
                    continue;
                }
            } else {
@@ -155,8 +155,8 @@
                );
                
                if (!transportingTasks.isEmpty()) {
                    log.info("站点{}有{}个正在搬运的{}AGV任务,跳过当前任务ID:{}",
                            staNo, transportingTasks.size(), taskType, task.getId());
                    // log.info("站点{}有{}个正在搬运的{}AGV任务,跳过当前任务ID:{}",
                    //         staNo, transportingTasks.size(), taskType, task.getId());
                    continue;
                }
            }
@@ -193,8 +193,8 @@
            
            // 如果重试次数已达到最大值,跳过本次发送
            if (retryEnabled && currentRetryCount >= maxRetryCount) {
                log.warn("{}呼叫agv搬运 - 任务ID:{}已达到最大重试次数({}),停止重试",
                        namespace, task.getId(), maxRetryCount);
                // log.warn("{}呼叫agv搬运 - 任务ID:{}已达到最大重试次数({}),停止重试",
                //         namespace, task.getId(), maxRetryCount);
                // 记录最终失败信息
                task.setErrorTime(new Date());
                task.setErrorMemo(String.format("AGV呼叫失败,已达到最大重试次数(%d次)", maxRetryCount));
@@ -203,12 +203,12 @@
            }
            
            // 打印请求信息(包含重试次数)
            if (currentRetryCount > 0) {
                log.info("{}呼叫agv搬运(第{}次重试) - 请求地址:{}", namespace, currentRetryCount + 1, url);
            } else {
                log.info("{}呼叫agv搬运 - 请求地址:{}", namespace, url);
            }
            log.info("{}呼叫agv搬运 - 请求参数:{}", namespace, body);
            // if (currentRetryCount > 0) {
            //     log.info("{}呼叫agv搬运(第{}次重试) - 请求地址:{}", namespace, currentRetryCount + 1, url);
            // } else {
            //     log.info("{}呼叫agv搬运 - 请求地址:{}", namespace, url);
            // }
            // log.info("{}呼叫agv搬运 - 请求参数:{}", namespace, body);
            
            try {
                // 使用仙工M4接口
@@ -219,12 +219,12 @@
                        .build()
                        .doPost();
                // 打印返回参数
                log.info("{}呼叫agv搬运 - 返回参数:{}", namespace, response);
                // log.info("{}呼叫agv搬运 - 返回参数:{}", namespace, response);
                
                // 检查响应是否为空
                if (response == null || response.trim().isEmpty()) {
                    String errorMsg = "AGV接口返回为空";
                    log.error("{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, task.getId(), errorMsg);
                    // log.error("{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, task.getId(), errorMsg);
                    handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount);
                    continue;
                }
@@ -232,7 +232,7 @@
                JSONObject jsonObject = JSON.parseObject(response);
                if (jsonObject == null) {
                    String errorMsg = "响应JSON解析失败,响应内容:" + response;
                    log.error("{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, task.getId(), errorMsg);
                    // log.error("{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, task.getId(), errorMsg);
                    handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount);
                    continue;
                }
@@ -246,17 +246,17 @@
                    task.setErrorTime(null);
                    task.setErrorMemo(null);
                    taskService.updateById(task);
                    log.info("{}呼叫agv搬运成功 - 任务ID:{}", namespace, task.getId());
                    // log.info("{}呼叫agv搬运成功 - 任务ID:{}", namespace, task.getId());
                } else {
                    String message = jsonObject.getString("message");
                    String errorMsg = String.format("错误码:%s,错误信息:%s", code, message);
                    log.error("{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, task.getId(), errorMsg);
                    // log.error("{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, task.getId(), errorMsg);
                    handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount);
                }
            } catch (Exception e) {
                String errorMsg = "异常信息:" + e.getMessage();
                log.error("{}呼叫agv搬运异常 - 任务ID:{},请求地址:{},请求参数:{},{}",
                        namespace, task.getId(), url, body, errorMsg, e);
                // log.error("{}呼叫agv搬运异常 - 任务ID:{},请求地址:{},请求参数:{},{}",
                //         namespace, task.getId(), url, body, errorMsg, e);
                handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount);
            } finally {
                try {
@@ -271,7 +271,7 @@
                            success
                    );
                } catch (Exception e) {
                    log.error(namespace + "呼叫agv保存接口日志异常:", e);
                    // log.error(namespace + "呼叫agv保存接口日志异常:", e);
                }
            }
        }
@@ -295,8 +295,8 @@
            task.setErrorTime(new Date());
            task.setErrorMemo(String.format("AGV呼叫失败(第%d次重试):%s", newRetryCount, errorMsg));
            taskService.updateById(task);
            log.info("{}呼叫agv搬运失败 - 任务ID:{},已重试{}次,将在下次定时任务时继续重试(最多{}次)",
                    namespace, task.getId(), newRetryCount, maxRetryCount);
            // log.info("{}呼叫agv搬运失败 - 任务ID:{},已重试{}次,将在下次定时任务时继续重试(最多{}次)",
            //         namespace, task.getId(), newRetryCount, maxRetryCount);
        } else {
            // 不启用重试或已达到最大重试次数,停止重试
            task.setErrorTime(new Date());
@@ -306,8 +306,8 @@
                task.setErrorMemo(String.format("AGV呼叫失败(重试未启用):%s", errorMsg));
            }
            taskService.updateById(task);
            log.warn("{}呼叫agv搬运失败 - 任务ID:{},停止重试。错误信息:{}",
                    namespace, task.getId(), errorMsg);
            // log.warn("{}呼叫agv搬运失败 - 任务ID:{},停止重试。错误信息:{}",
            //         namespace, task.getId(), errorMsg);
        }
    }
@@ -390,7 +390,7 @@
            fromBin = task.getSourceStaNo();
        }
        if (fromBin == null || fromBin.isEmpty() || "0".equals(fromBin)) {
            log.warn("任务{}的源库位和源站点都为空,使用默认值", task.getId());
            // log.warn("任务{}的源库位和源站点都为空,使用默认值", task.getId());
            fromBin = "0";
        }
        object.put("fromBin", fromBin);
@@ -449,12 +449,12 @@
            // 默认使用东侧
            targetStations = agvProperties.getEastStations();
            groupKey = "east";
            log.warn("任务ID:{}的机器人组{}未识别,使用默认东侧站点", task.getId(), robotGroup);
            // log.warn("任务ID:{}的机器人组{}未识别,使用默认东侧站点", task.getId(), robotGroup);
        }
        
        if (targetStations.isEmpty()) {
            String errorMsg = "没有可用的目标站点配置";
            log.warn("任务ID:{}", errorMsg, task.getId());
            // log.warn("任务ID:{}", errorMsg, task.getId());
            return errorMsg;
        }
        
@@ -476,7 +476,7 @@
        
        if (sites.isEmpty()) {
            String errorMsg = "没有能入站点";
            log.warn("任务ID:{}", errorMsg, task.getId());
            // log.warn("任务ID:{}", errorMsg, task.getId());
            return errorMsg;
        }
        
@@ -492,7 +492,7 @@
        
        if (devListWithConfig.isEmpty()) {
            // 站点配置不允许入库(canining != "Y"),暂不分配,等待配置开通(只在定时任务中记录日志)
            log.warn("任务ID:{}没有可入站点(站点未开通可入允许:canining='Y'),暂不分配站点,等待配置开通", task.getId());
            // log.warn("任务ID:{}没有可入站点(站点未开通可入允许:canining='Y'),暂不分配站点,等待配置开通", task.getId());
            return null; // 返回null,表示暂不分配,等待配置开通
        }
        
@@ -503,7 +503,7 @@
        List<Integer> canInSites = basDevpMapper.getCanInSites(configuredSites);
        if (canInSites.isEmpty()) {
            // 所有已配置可入的站点都有出库任务,暂不分配,等待下次定时任务再尝试(只在定时任务中记录日志)
            log.warn("任务ID:{}没有可入站点(请等待出库完成),暂不分配站点,等待下次定时任务再尝试", task.getId());
            // log.warn("任务ID:{}没有可入站点(请等待出库完成),暂不分配站点,等待下次定时任务再尝试", task.getId());
            return null; // 返回null,表示暂不分配,等待下次定时任务再尝试
        }
        
@@ -520,7 +520,7 @@
        if (devList.isEmpty()) {
            // 理论上不应该到这里,因为前面已经检查过了,但为了安全起见还是保留
            String errorMsg = "没有可入站点(in_enable='Y'且canining='Y')";
            log.warn("任务ID:{}", errorMsg, task.getId());
            // log.warn("任务ID:{}", errorMsg, task.getId());
            return errorMsg;
        }
        
@@ -599,15 +599,15 @@
                isTransporting = !transportingTasks.isEmpty();
                
                if (isTransporting) {
                    log.debug("站点{}有{}个正在搬运的{}AGV任务,检查下一个站点",
                        staNo, transportingTasks.size(), taskTypeName);
                    // log.debug("站点{}有{}个正在搬运的{}AGV任务,检查下一个站点",
                    //     staNo, transportingTasks.size(), taskTypeName);
                    continue; // 该站点正在搬运,检查下一个站点
                }
            }
            
            // 找到第一个空闲站点,分配
            selectedSite = dev;
            log.info("任务ID:{}按规则应分配到站点{},该站点空闲,分配成功", task.getId(), staNo);
            // log.info("任务ID:{}按规则应分配到站点{},该站点空闲,分配成功", task.getId(), staNo);
            break;
        }
        
@@ -623,7 +623,7 @@
        // 检查站点是否有效(不能为0或null)
        if (endSite == null || endSite == 0) {
            String errorMsg = String.format("分配的站点无效(dev_no=%s)", endSite);
            log.error("任务ID:{},{}", task.getId(), errorMsg);
            // log.error("任务ID:{},{}", task.getId(), errorMsg);
            return errorMsg;
        }
        
@@ -634,7 +634,7 @@
        task.setStaNo(String.valueOf(endSite));
        taskService.updateById(task);
        
        log.info("任务ID:{}已分配站点:{}", task.getId(), endSite);
        // log.info("任务ID:{}已分配站点:{}", task.getId(), endSite);
        return null; // 分配成功,返回null
    }
src/main/java/com/zy/asrs/task/handler/WorkMastHandler.java
@@ -629,9 +629,40 @@
    public ReturnT<String> agvDoOut(Task task) {
        if (task.getIoType().equals(101)) {
            Date now = new Date();
            // 检查sourceLocNo是否为空
            if (task.getSourceLocNo() == null || task.getSourceLocNo().isEmpty()) {
//                log.warn("任务ID:{}的sourceLocNo为空,跳过库位操作(可能是从站点入库的任务)", task.getId());
                task.setWrkSts(15L);
                if (!taskService.updateById(task)) {
                    throw new CoolException("任务状态修改失败!!");
                }
                return SUCCESS;
            }
            // 查询库位信息
            LocCache locMast = locCacheService.selectOne(new EntityWrapper<LocCache>().eq("loc_no", task.getSourceLocNo()));
            if (Objects.isNull(locMast)) {
                throw new RuntimeException("数据错误:库位信息不能为空!!");
                // 如果查询不到库位,可能是sourceLocNo是站点号而不是库位号
                // 检查BasDevp表中是否存在该站点
                try {
                    Integer siteNo = Integer.parseInt(task.getSourceLocNo());
                    List<BasDevp> basDevpList = basDevpMapper.selectList(new EntityWrapper<BasDevp>().eq("dev_no", siteNo));
                    if (basDevpList != null && !basDevpList.isEmpty()) {
                        // sourceLocNo是站点号,这是从站点入库的任务,不需要处理库位
//                        log.info("任务ID:{}的sourceLocNo是站点号:{},跳过库位操作(从站点入库任务)", task.getId(), task.getSourceLocNo());
                        task.setWrkSts(15L);
                        if (!taskService.updateById(task)) {
                            throw new CoolException("任务状态修改失败!!");
                        }
                        return SUCCESS;
                    }
                } catch (NumberFormatException e) {
                    // sourceLocNo不是数字,可能是库位号格式错误
                    log.warn("任务ID:{}的sourceLocNo:{}不是有效的数字格式", task.getId(), task.getSourceLocNo());
                }
                // 既不是库位也不是站点,抛出异常
                throw new RuntimeException("数据错误:库位信息不能为空!!任务ID:" + task.getId() + ",sourceLocNo:" + task.getSourceLocNo());
            }
            List<TaskDetl> wrkDetls101 = taskDetlService.selectList(new EntityWrapper<TaskDetl>().eq("wrk_no", task.getWrkNo()));
            if (wrkDetls101.isEmpty()) {
@@ -1182,9 +1213,42 @@
        if (ioType == 1 || ioType == 10 || ioType == 53 || ioType == 57) {
            // ioType == 1 需要处理组托数据
            if (ioType == 1) {
                // 检查locNo是否为空
                if (wrkMast.getLocNo() == null || wrkMast.getLocNo().isEmpty()) {
                    log.warn("任务ID:{}的locNo为空,跳过库位操作(可能是从站点入库的任务)", wrkMast.getId());
                    // 更新任务状态为5(库存更新完成)
                    wrkMast.setWrkSts(5L);
                    wrkMast.setModiTime(new Date());
                    if (!taskService.updateById(wrkMast)) {
                        throw new CoolException("任务状态修改失败!!");
                    }
                    return SUCCESS;
                }
                LocCache locCache = locCacheService.selectOne(new EntityWrapper<LocCache>().eq("loc_no", wrkMast.getLocNo()));
                if (Objects.isNull(locCache)) {
                    throw new CoolException("数据错误,库位不存在!!");
                    // 如果查询不到库位,可能是locNo是站点号而不是库位号
                    // 检查BasDevp表中是否存在该站点
                    try {
                        Integer siteNo = Integer.parseInt(wrkMast.getLocNo());
                        List<BasDevp> basDevpList = basDevpMapper.selectList(new EntityWrapper<BasDevp>().eq("dev_no", siteNo));
                        if (basDevpList != null && !basDevpList.isEmpty()) {
                            // locNo是站点号,这是从站点入库的任务,不需要处理库位明细
                            log.info("任务ID:{}的locNo是站点号:{},跳过库位操作(从站点入库任务)", wrkMast.getId(), wrkMast.getLocNo());
                            // 更新任务状态为5(库存更新完成)
                            wrkMast.setWrkSts(5L);
                            wrkMast.setModiTime(new Date());
                            if (!taskService.updateById(wrkMast)) {
                                throw new CoolException("任务状态修改失败!!");
                            }
                            return SUCCESS;
                        }
                    } catch (NumberFormatException e) {
                        // locNo不是数字,可能是库位号格式错误
                        log.warn("任务ID:{}的locNo:{}不是有效的数字格式", wrkMast.getId(), wrkMast.getLocNo());
                    }
                    // 既不是库位也不是站点,抛出异常
                    throw new CoolException("数据错误,库位不存在!!任务ID:" + wrkMast.getId() + ",locNo:" + wrkMast.getLocNo());
                }
                if (!locCache.getLocSts().equals(LocStsType.LOC_STS_TYPE_S.type)) {
                    throw new CoolException("当前库位状态为:" + LocStsType.LOC_STS_TYPE_S.type + "." + LocStsType.LOC_STS_TYPE_S.desc + ",不是出库预约状态");
src/main/java/com/zy/system/controller/UserController.java
@@ -120,6 +120,12 @@
        if (user.getDeptId() != null) {
            entity.setDeptId(user.getDeptId());
        }
        if (user.getAdLogin() != null) {
            entity.setAdLogin(user.getAdLogin());
        }
        if (user.getEmail() != null) {
            entity.setEmail(user.getEmail());
        }
        userService.updateById(entity);
        return R.ok();
    }
src/main/java/com/zy/system/entity/User.java
@@ -92,6 +92,12 @@
     */
    private Integer status;
    /**
     * AD登录 1: 启用 0: 禁用
     */
    @TableField("ad_login")
    private Integer adLogin;
    public Long getId() {
        return id;
    }
@@ -260,4 +266,26 @@
        this.status = status;
    }
    public Integer getAdLogin() {
        return adLogin;
    }
    public void setAdLogin(Integer adLogin) {
        this.adLogin = adLogin;
    }
    public String getAdLogin$() {
        if (null == this.adLogin) {
            return "禁用";
        }
        switch (this.adLogin) {
            case 1:
                return "启用";
            case 0:
                return "禁用";
            default:
                return String.valueOf(this.adLogin);
        }
    }
}
src/main/resources/logback-spring.xml
@@ -10,9 +10,10 @@
    <property name="CONSOLE_LOG_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(-%5p) ${PID:-} [%15.15t] %-40.40logger{39} : %m%n">
    </property>
    <property name="LOG_PATH" value="/stock/out/wms/logs"/>
    <!-- 日志路径:D盘(Windows环境),Linux环境使用/stock/out/wms/logs -->
    <property name="LOG_PATH" value="D:/stock/out/wms/logs"/>
    <!--控制台-->
    <!--控制台输出-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
@@ -20,23 +21,27 @@
        </encoder>
    </appender>
    <!--info级别-->
    <!--INFO级别日志-->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}/info_%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <maxHistory>10</maxHistory>
            <!-- 保留15天 -->
            <maxHistory>15</maxHistory>
            <!-- 总大小限制15GB(防止日志占用过多空间) -->
            <totalSizeCap>15GB</totalSizeCap>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
                <!-- 单个文件100MB(减少文件数量) -->
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg %n</pattern>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!--error级别-->
    <!--ERROR级别日志-->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
@@ -44,13 +49,39 @@
        <file>${LOG_PATH}/error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}/error_%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <maxHistory>10</maxHistory>
            <!-- 错误日志保留30天 -->
            <maxHistory>30</maxHistory>
            <!-- 错误日志总大小限制5GB -->
            <totalSizeCap>5GB</totalSizeCap>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg %n</pattern>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!--WARN级别日志(可选)-->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <file>${LOG_PATH}/warn.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}/warn_%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <!-- 警告日志保留15天 -->
            <maxHistory>15</maxHistory>
            <totalSizeCap>2GB</totalSizeCap>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>50MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
@@ -59,5 +90,6 @@
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="INFO_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
        <appender-ref ref="WARN_FILE"/>
    </root>
</configuration>
src/main/resources/mapper/UserMapper.xml
@@ -16,6 +16,7 @@
        <result column="sex" property="sex" />
        <result column="create_time" property="createTime" />
        <result column="status" property="status" />
        <result column="ad_login" property="adLogin" />
    </resultMap>
src/main/webapp/static/js/user/user.js
@@ -31,6 +31,7 @@
            ,{field: 'roleName', align: 'center',title: '角色'}
            ,{field: 'email', align: 'center',title: '邮箱'}
            // ,{field: 'sex$', align: 'center',title: '性别'}
            ,{field: 'adLogin$', align: 'center',title: 'AD登录', templet: '#adLoginTpl', width: 100, unresize: true}
            ,{field: 'createTime$', align: 'center',title: '注册时间', hide: true}
            ,{field: 'status$', align: 'center',title: '状态', templet: '#statusTpl', width: 120, unresize: true}
@@ -92,6 +93,16 @@
        })
    })
    // 修改AD登录状态
    form.on('switch(adLoginSwitch)', function (obj) {
        var index  = obj.othis.parents('tr').attr("data-index");
        var data = tableData[index];
        data[this.name] = obj.elem.checked?1:0;
        http.post(baseUrl+"/user/edit/auth", {id: data.id, adLogin: data[this.name]}, function (res) {
            layer.msg(res.msg, {icon: 1});
        })
    })
    /* 表格2工具条点击事件 */
    table.on('tool(userTable)', function (obj) {
        var data = obj.data;
@@ -132,7 +143,31 @@
            content: $('#editDialog').html(),
            success: function (layero, dIndex) {
                // 回显表单数据
                if (mData) {
                form.val('detail', mData);
                    // 处理AD登录复选框的回显(编辑用户时)
                    if (mData.adLogin === 1 || mData.adLogin === '1') {
                        $('input[name="adLogin"]').prop('checked', true);
                    } else {
                        $('input[name="adLogin"]').prop('checked', false);
                    }
                } else {
                    // 新增用户时,确保AD登录默认为未选中
                    $('input[name="adLogin"]').prop('checked', false);
                    // 清空表单
                    form.val('detail', {
                        username: '',
                        mobile: '',
                        email: '',
                        roleId: '',
                        roleName: '',
                        deptId: '',
                        deptName: '',
                        adLogin: 0
                    });
                }
                // 重新渲染表单,确保开关控件正确显示
                form.render('checkbox');
                // 表单提交事件
                form.on('submit(editSubmit)', function (data) {
                    if (isEmpty(data.field.roleId)) {
@@ -143,6 +178,12 @@
                        layer.msg('请选择部门', {icon: 2});
                        return false;
                    }
                    // 处理AD登录复选框的值
                    if (data.field.adLogin === '1' || data.field.adLogin === true) {
                        data.field.adLogin = 1;
                    } else {
                        data.field.adLogin = 0;
                    }
                    var loadIndex = layer.load(2);
                    $.ajax({
                        url: baseUrl+"/user/"+(mData?'update':'add')+"/auth",
src/main/webapp/views/user/user.html
@@ -67,6 +67,10 @@
    <input type="checkbox" name="status" value="{{d.status}}" lay-skin="switch" lay-text="正常|禁用" lay-filter="statusSwitch" {{ d.status === 1 ? 'checked' : '' }}>
</script>
    <script type="text/html" id="adLoginTpl">
    <input type="checkbox" name="adLogin" value="{{d.adLogin}}" lay-skin="switch" lay-text="启用|禁用" lay-filter="adLoginSwitch" {{ d.adLogin === 1 ? 'checked' : '' }}>
</script>
    <script type="text/html" id="operate">
    <a class="layui-btn layui-btn-xs btn-edit" lay-event="edit">编辑</a>
    <a class="layui-btn layui-btn-xs btn-edit layui-btn-warm" lay-event="resetPwd">重置密码</a>
@@ -150,10 +154,20 @@
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <hr class="layui-bg-gray">
        <div class="layui-row">
            <div class="layui-col-md12">
                <div class="layui-form-item">
                    <label class="layui-form-label">AD登录</label>
                    <div class="layui-input-block">
                        <input type="checkbox" name="adLogin" value="1" title="启用AD登录" lay-skin="switch" lay-text="启用|禁用" lay-filter="adLoginFormSwitch">
                        <div class="layui-form-mid layui-word-aux" style="margin-left: 10px;">开启后,用户可以使用AD域账号登录系统</div>
                    </div>
                </div>
            </div>
        </div>
        <div class="layui-form-item text-right">
            <button class="layui-btn" lay-filter="editSubmit" lay-submit="">保存</button>
            <button class="layui-btn layui-btn-primary" type="button" ew-event="closeDialog">取消</button>