huang
8 天以前 73aa66976e35252378be3f09be2474193ccd0bf6
修改任务执行步骤状态完成检验
3个文件已修改
420 ■■■■■ 已修改文件
mes-common/serverBase/src/main/java/com/mes/exception/GlobalExceptionHandler.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java 310 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-common/serverBase/src/main/java/com/mes/exception/GlobalExceptionHandler.java
@@ -2,9 +2,15 @@
import com.mes.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.servlet.http.HttpServletRequest;
/**
 * @author zhoush
@@ -22,10 +28,35 @@
        return Result.error(se.getCode(), se.getMessage());
    }
    /**
     * 处理媒体类型不匹配异常(通常发生在SSE连接已关闭时)
     */
    @ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
    @ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
    @ResponseBody
    public Result<Object> handleMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException e, HttpServletRequest request) {
        // 如果是SSE请求,静默处理,不记录错误
        String acceptHeader = request.getHeader("Accept");
        if (acceptHeader != null && acceptHeader.contains("text/event-stream")) {
            log.debug("SSE连接已关闭,忽略媒体类型异常: {}", e.getMessage());
            return null; // 返回null,不写入响应
        }
        log.warn("媒体类型不匹配: {}", e.getMessage());
        return Result.error();
    }
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result<Object> error(Exception e) {
        e.printStackTrace();
    public Result<Object> error(Exception e, HttpServletRequest request) {
        // 如果是SSE请求且是媒体类型异常,静默处理
        if (e instanceof HttpMediaTypeNotAcceptableException) {
            String acceptHeader = request.getHeader("Accept");
            if (acceptHeader != null && acceptHeader.contains("text/event-stream")) {
                log.debug("SSE连接已关闭,忽略异常: {}", e.getMessage());
                return null;
            }
        }
        log.error("全局异常处理: ", e);
        return Result.error();
    }
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java
@@ -112,6 +112,10 @@
    
    // 记录当前任务:deviceId -> 任务信息
    private final Map<String, MesTaskInfo> currentTasks = new ConcurrentHashMap<>();
    /**
     * 记录最近一次已完成但MES未复位的任务签名,避免重复拉起
     */
    private final Map<String, CompletedMesRecord> lastCompletedMesRecords = new ConcurrentHashMap<>();
    @Autowired
    public LoadVehicleLogicHandler(
@@ -145,6 +149,10 @@
                // 因为定时器可能会重复调用,或者需要等待任务完成
                if ("feedGlass".equals(operation) && status.isExecuting()) {
                    log.debug("车辆 {} 当前状态为 EXECUTING,但允许继续执行 feedGlass(定时器重复调用)", deviceId);
                    // 允许继续执行,不返回错误
                } else if ("clearGlass".equals(operation) || "clearPlc".equals(operation) || "clear".equals(operation)) {
                    // 清除操作应该允许在任何状态下执行,因为其目的就是重置设备状态
                    log.debug("车辆 {} 当前状态为 {},但允许执行清除操作 {}", deviceId, status.getState(), operation);
                    // 允许继续执行,不返回错误
                } else {
                    return DevicePlcVO.OperationResult.builder()
@@ -1356,6 +1364,25 @@
                        .build();
            }
            
            // 构建当前MES数据签名,用于判断是否为已确认但未复位的旧任务
            String mesSignature = buildMesSignature(mesData);
            CompletedMesRecord completedRecord = lastCompletedMesRecords.get(deviceId);
            if (completedRecord != null
                    && mesSignature.equals(completedRecord.signature)) {
                Integer mesConfirm = parseInteger(mesData.get("mesConfirm"));
                if (mesConfirm != null && mesConfirm == 1) {
                    Map<String, Object> waitData = new HashMap<>();
                    waitData.put("completed", true);
                    waitData.put("waiting", true);
                    waitData.put("waitingReason", "mesNotReset");
                    return DevicePlcVO.OperationResult.builder()
                            .success(true)
                            .message("MES已确认完成但未复位(mesSend/mesConfirm),等待复位后再接收新任务")
                            .data(waitData)
                            .build();
                }
            }
            // mesSend=1,记录日志
            log.info("检测到mesSend=1,开始读取MES任务信息: deviceId={}", deviceId);
            
@@ -1453,6 +1480,7 @@
            taskInfo.cartime = timeCalc.cartime;
            taskInfo.createdTime = System.currentTimeMillis();
            taskInfo.isOutbound = isOutbound;
            taskInfo.mesSignature = mesSignature;
            
            // 从配置中读取破损玻璃索引(用于测试场景)
            // 配置格式:brokenGlassIndices: [0, 2] 表示第1个和第3个玻璃应该破损
@@ -2075,9 +2103,14 @@
                data.put("completed", false);
                data.put("waiting", true);
                data.put("waitingReason", "waitingReport");
                String detail = taskInfo.currentStepDesc;
                String message = "大车任务执行中,尚未汇报,无需检查确认";
                if (detail != null && !detail.isEmpty()) {
                    message = detail + ";" + message;
                }
                return DevicePlcVO.OperationResult.builder()
                        .success(true)
                        .message("大车任务执行中,尚未汇报,无需检查确认")
                        .message(message)
                        .data(data)
                        .build();
            }
@@ -2118,6 +2151,10 @@
            if (completed) {
                // MES已确认,清空state和汇报字
                clearTaskStates(deviceConfig, serializer);
                // 记录已完成的任务签名,避免MES未复位时被重复拉起
                lastCompletedMesRecords.put(deviceId,
                        new CompletedMesRecord(taskInfo.mesSignature, System.currentTimeMillis()));
                // 任务完成,恢复为空闲状态
                statusManager.updateVehicleStatus(
@@ -2321,6 +2358,20 @@
        List<Integer> brokenGlassIndices = null; // 标记为破损的玻璃索引列表(0-based,用于测试场景)
        Long mesConfirmStartTime = null; // MES确认开始等待的时间(用于超时检测)
        String currentStepDesc = null; // 当前步骤描述(用于显示详细的执行状态)
        String mesSignature = null; // MES数据签名,用于避免未复位时重复拉起
    }
    /**
     * 记录已完成但MES未复位的任务信息
     */
    private static class CompletedMesRecord {
        final String signature;
        final long completedAt;
        CompletedMesRecord(String signature, long completedAt) {
            this.signature = signature;
            this.completedAt = completedAt;
        }
    }
    /**
@@ -2399,5 +2450,27 @@
            log.warn("清空大车state字段失败: deviceId={}, error={}", deviceConfig != null ? deviceConfig.getId() : "null", e.getMessage());
        }
    }
    /**
     * 将MES数据构造成签名字符串,用于识别是否为同一批次任务
     */
    private String buildMesSignature(Map<String, Object> mesData) {
        if (mesData == null || mesData.isEmpty()) {
            return "empty";
        }
        StringBuilder sb = new StringBuilder();
        sb.append("mesSend=").append(mesData.getOrDefault("mesSend", ""))
                .append(";mesConfirm=").append(mesData.getOrDefault("mesConfirm", ""));
        for (int i = 1; i <= 6; i++) {
            sb.append(";g").append(i).append("=")
                    .append(mesData.getOrDefault("mesGlassId" + i, ""))
                    .append(",s").append(mesData.getOrDefault("start" + i, ""))
                    .append(",t").append(mesData.getOrDefault("target" + i, ""))
                    .append(",w").append(mesData.getOrDefault("mesWidth" + i, ""))
                    .append(",h").append(mesData.getOrDefault("mesHeight" + i, ""))
                    .append(",th").append(mesData.getOrDefault("mesThickness" + i, ""));
        }
        return sb.toString();
    }
}
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java
@@ -676,21 +676,72 @@
                                task.getTaskId(), device.getId());
                        return;
                    }
                    // 已完成/失败的步骤不再回退状态
                    if (TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())
                            || TaskStepDetail.Status.FAILED.name().equals(step.getStatus())) {
                        return;
                    }
                    // 进片大车设备:只有在真正开始处理时才设置为RUNNING
                    // 先检查卧转立设备是否已完成,如果还在执行中,不应该开始大车的工作
                    boolean transferCompleted = isTransferDeviceCompleted(task.getTaskId(), context);
                    if (!transferCompleted) {
                        // 卧转立还在执行中,等待卧转立完成
                        if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus())) {
                            step.setStatus(TaskStepDetail.Status.PENDING.name());
                            step.setSuccessMessage("等待卧转立完成");
                            if (step.getStartTime() == null) {
                                step.setStartTime(new Date());
                            }
                            taskStepDetailMapper.updateById(step);
                            notificationService.notifyStepUpdate(task.getTaskId(), step);
                        }
                        log.debug("卧转立还在执行中,进片大车等待: taskId={}, deviceId={}",
                                task.getTaskId(), device.getId());
                        return;
                    }
                    // 检查是否有卧转立主体已输出、准备上大车的玻璃信息
                    List<String> readyGlassIds = getTransferReadyGlassIds(context);
                    
                    // 如果当前没有新的玻璃,无论步骤是否已进入RUNNING,都应该轮询MES任务/确认状态
                    // 如果当前没有新的玻璃,但卧转立已完成,可以轮询MES任务/确认状态
                    if (CollectionUtils.isEmpty(readyGlassIds)) {
                        // 轮询MES任务/确认,避免错过MES侧后写入的任务
                        pollMesForVehicle(task, step, device, context);
                        // 如果仍然没有卧转立输出的玻璃,保持/更新为PENDING提示
                        if (!TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())
                                && !TaskStepDetail.Status.PENDING.name().equals(step.getStatus())) {
                            step.setStatus(TaskStepDetail.Status.PENDING.name());
                            step.setSuccessMessage("等待卧转立输出玻璃");
                            taskStepDetailMapper.updateById(step);
                            notificationService.notifyStepUpdate(task.getTaskId(), step);
                        // 检查是否所有初始玻璃都已装载
                        @SuppressWarnings("unchecked")
                        List<String> initialGlassIds = (List<String>) context.getSharedData().get("initialGlassIds");
                        List<String> loadedGlassIds = getLoadedGlassIds(context);
                        if (initialGlassIds != null && !initialGlassIds.isEmpty()) {
                            // 如果所有初始玻璃都已装载,说明已经处理完,不应该再变回等待
                            if (loadedGlassIds != null && !loadedGlassIds.isEmpty()
                                    && loadedGlassIds.containsAll(initialGlassIds)) {
                                // 所有玻璃都已装载,保持RUNNING状态,继续轮询MES任务/确认
                                if (!TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())) {
                                    step.setStatus(TaskStepDetail.Status.RUNNING.name());
                                    if (step.getStartTime() == null) {
                                        step.setStartTime(new Date());
                                    }
                                    taskStepDetailMapper.updateById(step);
                                    notificationService.notifyStepUpdate(task.getTaskId(), step);
                                }
                                pollMesForVehicle(task, step, device, context);
                                return;
                            }
                        }
                        // 如果大车已经装载过玻璃(RUNNING状态),轮询MES任务/确认状态
                        if (TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())) {
                            pollMesForVehicle(task, step, device, context);
                        } else {
                            // 如果还没有装载过玻璃,等待卧转立输出
                            if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus())) {
                                step.setStatus(TaskStepDetail.Status.PENDING.name());
                                step.setSuccessMessage("等待卧转立输出玻璃");
                                if (step.getStartTime() == null) {
                                    step.setStartTime(new Date());
                                }
                                taskStepDetailMapper.updateById(step);
                                notificationService.notifyStepUpdate(task.getTaskId(), step);
                            }
                        }
                        return;
                    }
@@ -784,9 +835,48 @@
                                    task.getTaskId(), device.getId(), e.getMessage());
                        }
                        
                        // 如果MES已确认完成(mesConfirm=1),检查卧转立设备状态和玻璃信息
                        // 如果卧转立已完成且所有玻璃都已装载,可以标记为完成
                        if (mesResult != null && mesResult.getData() != null) {
                            Object completedFlag = mesResult.getData().get("completed");
                            boolean mesConfirmed = false;
                            if (completedFlag instanceof Boolean) {
                                mesConfirmed = (Boolean) completedFlag;
                            } else if (completedFlag != null) {
                                mesConfirmed = "true".equalsIgnoreCase(String.valueOf(completedFlag));
                            }
                            if (mesConfirmed) {
                                // MES已确认完成,检查卧转立设备是否已完成
                                boolean transferCompletedForMes = isTransferDeviceCompleted(task.getTaskId(), context);
                                if (transferCompletedForMes) {
                                    // 检查任务上下文中的初始玻璃ID和已装载的玻璃ID
                                    @SuppressWarnings("unchecked")
                                    List<String> initialGlassIds = (List<String>) context.getSharedData().get("initialGlassIds");
                                    List<String> loadedGlassIds = getLoadedGlassIds(context);
                                    if (initialGlassIds != null && !initialGlassIds.isEmpty()
                                            && loadedGlassIds != null && !loadedGlassIds.isEmpty()) {
                                        // 检查是否所有初始玻璃都已装载
                                        boolean allGlassesLoaded = loadedGlassIds.containsAll(initialGlassIds);
                                        if (allGlassesLoaded) {
                                            // 卧转立已完成且所有玻璃都已装载,标记为完成
                                            log.info("MES已确认且卧转立已完成且所有玻璃已装载,任务自动完成: taskId={}, deviceId={}, initialCount={}, loadedCount={}",
                                                    task.getTaskId(), device.getId(), initialGlassIds.size(), loadedGlassIds.size());
                                            // mesResult已经包含completed=true,不需要修改
                                        }
                                    }
                                } else {
                                    // 卧转立还未完成,不应该标记为完成
                                    log.debug("MES已确认但卧转立未完成,等待卧转立完成: taskId={}, deviceId={}",
                                            task.getTaskId(), device.getId());
                                }
                            }
                        }
                        // 更新步骤状态(大车设备保持RUNNING,直到MES确认完成或任务取消)
                        if (mesResult != null) {
                            updateStepStatusForVehicle(step, mesResult);
                            updateStepStatusForVehicle(task.getTaskId(), step, mesResult);
                            boolean opSuccess = Boolean.TRUE.equals(mesResult.getSuccess());
                            updateTaskProgress(task, step.getStepOrder(), opSuccess);
                            if (!opSuccess) {
@@ -794,7 +884,7 @@
                                        DeviceCoordinationService.DeviceStatus.FAILED, context);
                            }
                        } else {
                            updateStepStatusForVehicle(step, feedResult);
                            updateStepStatusForVehicle(task.getTaskId(), step, feedResult);
                            boolean opSuccess = Boolean.TRUE.equals(feedResult.getSuccess());
                            updateTaskProgress(task, step.getStepOrder(), opSuccess);
                            if (!opSuccess) {
@@ -844,10 +934,10 @@
                    List<String> processedGlassIds = getProcessedGlassIds(context);
                    boolean isRunning = TaskStepDetail.Status.RUNNING.name().equals(step.getStatus());
                    
                    // 如果设备已经在运行中,即使没有新玻璃,也要继续监控MES任务和确认状态
                    // 如果没有已处理玻璃,则不应主动把步骤拉到RUNNING,只保持已运行状态
                    if (CollectionUtils.isEmpty(processedGlassIds)) {
                        if (isRunning) {
                            // 设备正在运行中,先检查MES任务,然后监控MES确认状态
                            // 已经在运行的情况下,继续轮询MES任务/确认,避免错过确认
                            DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
                            if (handler != null) {
                                Map<String, Object> logicParams = parseLogicParams(device);
@@ -876,7 +966,7 @@
                                
                                // 更新步骤状态(大车设备保持RUNNING,直到MES确认完成或任务取消)
                                if (mesResult != null) {
                                    updateStepStatusForVehicle(step, mesResult);
                                    updateStepStatusForVehicle(task.getTaskId(), step, mesResult);
                                    boolean opSuccess = Boolean.TRUE.equals(mesResult.getSuccess());
                                    updateTaskProgress(task, step.getStepOrder(), opSuccess);
                                    if (!opSuccess) {
@@ -885,10 +975,10 @@
                                    }
                                }
                            }
                            return;
                        } else {
                            // 没有数据,且设备未运行,保持PENDING状态
                            if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus())) {
                            // 未运行且没有已处理玻璃,保持PENDING
                            if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus())
                                    && !TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())) {
                                step.setStatus(TaskStepDetail.Status.PENDING.name());
                                step.setSuccessMessage("等待大理片笼处理完成");
                                taskStepDetailMapper.updateById(step);
@@ -896,8 +986,8 @@
                            }
                            log.debug("出片大车设备定时器:暂无已处理的玻璃信息: taskId={}, deviceId={}",
                                    task.getTaskId(), device.getId());
                            return;
                        }
                        return;
                    }
                    
                    log.debug("出片大车设备定时器检测到已处理的玻璃信息: taskId={}, deviceId={}, glassCount={}",
@@ -974,7 +1064,7 @@
                        
                        // 更新步骤状态(大车设备保持RUNNING,直到MES确认完成或任务取消)
                        if (mesResult != null) {
                            updateStepStatusForVehicle(step, mesResult);
                            updateStepStatusForVehicle(task.getTaskId(), step, mesResult);
                            boolean opSuccess = Boolean.TRUE.equals(mesResult.getSuccess());
                            updateTaskProgress(task, step.getStepOrder(), opSuccess);
                            if (!opSuccess) {
@@ -982,7 +1072,7 @@
                                        DeviceCoordinationService.DeviceStatus.FAILED, context);
                            }
                        } else {
                            updateStepStatusForVehicle(step, feedResult);
                            updateStepStatusForVehicle(task.getTaskId(), step, feedResult);
                            boolean opSuccess = Boolean.TRUE.equals(feedResult.getSuccess());
                            updateTaskProgress(task, step.getStepOrder(), opSuccess);
                            if (!opSuccess) {
@@ -1030,14 +1120,57 @@
                                task.getTaskId(), device.getId());
                        return;
                    }
                    // 如果步骤已经完成,不再处理
                    if (TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())) {
                        // 检查是否所有初始玻璃都已处理完
                        @SuppressWarnings("unchecked")
                        List<String> initialGlassIds = (List<String>) context.getSharedData().get("initialGlassIds");
                        List<String> processedGlassIds = getProcessedGlassIds(context);
                        if (initialGlassIds != null && !initialGlassIds.isEmpty()
                                && processedGlassIds != null && !processedGlassIds.isEmpty()
                                && processedGlassIds.containsAll(initialGlassIds)) {
                            // 所有玻璃都已处理完,保持完成状态
                            log.debug("大理片笼设备已完成且所有玻璃已处理: taskId={}, deviceId={}, initialCount={}, processedCount={}",
                                    task.getTaskId(), device.getId(), initialGlassIds.size(), processedGlassIds.size());
                            return;
                        }
                    }
                    // 大理片笼设备:只有在真正开始处理时才设置为RUNNING
                    // 检查是否有已装载的玻璃信息(从进片大车来的)
                    List<String> loadedGlassIds = getLoadedGlassIds(context);
                    if (CollectionUtils.isEmpty(loadedGlassIds)) {
                        // 没有数据,保持WAITING状态和PENDING步骤状态
                        // 没有数据,检查是否所有玻璃都已处理完
                        @SuppressWarnings("unchecked")
                        List<String> initialGlassIds = (List<String>) context.getSharedData().get("initialGlassIds");
                        List<String> processedGlassIds = getProcessedGlassIds(context);
                        if (initialGlassIds != null && !initialGlassIds.isEmpty()
                                && processedGlassIds != null && !processedGlassIds.isEmpty()
                                && processedGlassIds.containsAll(initialGlassIds)) {
                            // 所有玻璃都已处理完,标记为完成
                            if (!TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())) {
                                step.setStatus(TaskStepDetail.Status.COMPLETED.name());
                                step.setSuccessMessage("所有玻璃已处理完成");
                                if (step.getEndTime() == null) {
                                    step.setEndTime(new Date());
                                }
                                if (step.getStartTime() != null && step.getEndTime() != null) {
                                    step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
                                }
                                taskStepDetailMapper.updateById(step);
                                notificationService.notifyStepUpdate(task.getTaskId(), step);
                                checkAndCompleteTaskIfDone(step.getTaskId());
                            }
                            return;
                        }
                        // 没有数据且未完成,保持WAITING状态和PENDING步骤状态
                        deviceCoordinationService.syncDeviceStatus(device,
                                DeviceCoordinationService.DeviceStatus.WAITING, context);
                        if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus())) {
                        if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus())
                                && !TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())) {
                            step.setStatus(TaskStepDetail.Status.PENDING.name());
                            step.setSuccessMessage("等待进片大车装载玻璃");
                            taskStepDetailMapper.updateById(step);
@@ -1141,7 +1274,14 @@
     */
    private void setLoadedGlassIds(TaskExecutionContext context, List<String> glassIds) {
        if (context != null) {
            context.getSharedData().put("loadedGlassIds", new ArrayList<>(glassIds));
            // 累加记录,避免后续 containsAll 判断因覆盖丢失历史玻璃而回退为等待
            List<String> merged = new ArrayList<>(getLoadedGlassIds(context)); // 确保可变
            if (glassIds != null) {
                merged.addAll(glassIds);
            }
            // 去重
            List<String> distinct = merged.stream().distinct().collect(java.util.stream.Collectors.toList());
            context.getSharedData().put("loadedGlassIds", distinct);
        }
    }
    
@@ -1438,6 +1578,9 @@
        }
        step.setOutputData(toJson(result));
        taskStepDetailMapper.updateById(step);
        if (StringUtils.hasText(step.getTaskId())) {
            notificationService.notifyStepUpdate(step.getTaskId(), step);
        }
    }
    
    /**
@@ -1508,8 +1651,14 @@
    /**
     * 更新大车设备步骤状态(保持RUNNING,直到手动停止或任务取消;失败时标记为FAILED)
     */
    private void updateStepStatusForVehicle(TaskStepDetail step, DevicePlcVO.OperationResult result) {
    private void updateStepStatusForVehicle(String taskId, TaskStepDetail step, DevicePlcVO.OperationResult result) {
        if (step == null || result == null) {
            return;
        }
        // 如果步骤已经处于完成或失败状态,则不再被重复更新(防止状态反复切换)
        if (TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())
                || TaskStepDetail.Status.FAILED.name().equals(step.getStatus())) {
            log.debug("步骤已完成或失败,不再更新状态: stepId={}, status={}", step.getId(), step.getStatus());
            return;
        }
        boolean success = Boolean.TRUE.equals(result.getSuccess());
@@ -1546,16 +1695,20 @@
        
        if (success && !completed) {
            // 成功但未完成:根据waiting状态决定显示为等待还是执行中
            if (waiting) {
            // 注意:如果步骤已经是RUNNING状态(说明已经装载过玻璃),不应该改回PENDING
            boolean isAlreadyRunning = TaskStepDetail.Status.RUNNING.name().equals(step.getStatus());
            if (waiting && !isAlreadyRunning) {
                // 只有在还没有开始运行时,才设置为PENDING
                step.setStatus(TaskStepDetail.Status.PENDING.name());
            } else {
                // 如果已经运行过,或者不是等待状态,保持或设置为RUNNING
                step.setStatus(TaskStepDetail.Status.RUNNING.name());
            }
            String message = result.getMessage();
            if (!StringUtils.hasText(message) && waiting) {
                message = "大车设备等待中" + (StringUtils.hasText(waitingReason) ? "(" + waitingReason + ")" : "");
            }
            step.setSuccessMessage(StringUtils.hasText(message) ? message : (waiting ? "大车设备等待中" : "大车设备运行中"));
            step.setSuccessMessage(StringUtils.hasText(message) ? message : (waiting && !isAlreadyRunning ? "大车设备等待中" : "大车设备运行中"));
            step.setErrorMessage(null);
            if (step.getStartTime() != null) {
                step.setDurationMs(now.getTime() - step.getStartTime().getTime());
@@ -1589,6 +1742,8 @@
        step.setOutputData(toJson(result));
        taskStepDetailMapper.updateById(step);
        // 通知前端步骤状态已更新
        notificationService.notifyStepUpdate(taskId, step);
    }
    /**
@@ -1629,9 +1784,48 @@
                        task.getTaskId(), device.getId(), e.getMessage());
            }
            // 如果MES已确认完成(mesConfirm=1),检查卧转立设备状态和玻璃信息
            // 如果卧转立已完成且所有玻璃都已装载,可以标记为完成
            if (mesResult != null && mesResult.getData() != null) {
                Object completedFlag = mesResult.getData().get("completed");
                boolean mesConfirmed = false;
                if (completedFlag instanceof Boolean) {
                    mesConfirmed = (Boolean) completedFlag;
                } else if (completedFlag != null) {
                    mesConfirmed = "true".equalsIgnoreCase(String.valueOf(completedFlag));
                }
                if (mesConfirmed) {
                    // MES已确认完成,检查卧转立设备是否已完成
                    boolean transferCompleted = isTransferDeviceCompleted(task.getTaskId(), context);
                    if (transferCompleted) {
                        // 检查任务上下文中的初始玻璃ID和已装载的玻璃ID
                        @SuppressWarnings("unchecked")
                        List<String> initialGlassIds = (List<String>) context.getSharedData().get("initialGlassIds");
                        List<String> loadedGlassIds = getLoadedGlassIds(context);
                        if (initialGlassIds != null && !initialGlassIds.isEmpty()
                                && loadedGlassIds != null && !loadedGlassIds.isEmpty()) {
                            // 检查是否所有初始玻璃都已装载
                            boolean allGlassesLoaded = loadedGlassIds.containsAll(initialGlassIds);
                            if (allGlassesLoaded) {
                                // 卧转立已完成且所有玻璃都已装载,标记为完成
                                log.info("MES已确认且卧转立已完成且所有玻璃已装载,任务自动完成: taskId={}, deviceId={}, initialCount={}, loadedCount={}",
                                        task.getTaskId(), device.getId(), initialGlassIds.size(), loadedGlassIds.size());
                                // mesResult已经包含completed=true,不需要修改
                            }
                        }
                    } else {
                        // 卧转立还未完成,不应该标记为完成
                        log.debug("MES已确认但卧转立未完成,等待卧转立完成: taskId={}, deviceId={}",
                                task.getTaskId(), device.getId());
                    }
                }
            }
            // 更新步骤状态
            if (mesResult != null) {
                updateStepStatusForVehicle(step, mesResult);
                updateStepStatusForVehicle(task.getTaskId(), step, mesResult);
                boolean opSuccess = Boolean.TRUE.equals(mesResult.getSuccess());
                updateTaskProgress(task, step.getStepOrder(), opSuccess);
                if (!opSuccess) {
@@ -2455,6 +2649,66 @@
        }
    }
    /**
     * 检查卧转立设备是否已完成
     * 返回true表示卧转立已完成(COMPLETED),可以判断大车是否完成
     * 返回false表示卧转立还在运行中(RUNNING)或等待中(PENDING),不应该标记大车为完成
     */
    private boolean isTransferDeviceCompleted(String taskId, TaskExecutionContext context) {
        if (taskId == null || context == null) {
            return false;
        }
        try {
            // 从上下文中获取设备列表
            @SuppressWarnings("unchecked")
            List<DeviceConfig> devices = (List<DeviceConfig>) context.getSharedData().get("devices");
            if (devices == null || devices.isEmpty()) {
                return false;
            }
            // 查找卧转立设备
            DeviceConfig transferDevice = null;
            for (DeviceConfig device : devices) {
                if (DeviceConfig.DeviceType.WORKSTATION_TRANSFER.equals(device.getDeviceType())) {
                    transferDevice = device;
                    break;
                }
            }
            if (transferDevice == null) {
                // 没有卧转立设备,返回true(不影响判断)
                return true;
            }
            // 查找卧转立设备的步骤(应该只有一个步骤)
            List<TaskStepDetail> transferSteps = taskStepDetailMapper.selectList(
                Wrappers.<TaskStepDetail>lambdaQuery()
                    .eq(TaskStepDetail::getTaskId, taskId)
                    .eq(TaskStepDetail::getDeviceId, transferDevice.getId())
                    .orderByDesc(TaskStepDetail::getStepOrder)
                    .last("LIMIT 1")
            );
            if (transferSteps == null || transferSteps.isEmpty()) {
                // 没有找到步骤,返回false(卧转立可能还没开始)
                return false;
            }
            // 检查步骤状态:只有COMPLETED才算完成,RUNNING或PENDING都不算完成
            TaskStepDetail transferStep = transferSteps.get(0);
            String status = transferStep.getStatus();
            boolean isCompleted = TaskStepDetail.Status.COMPLETED.name().equals(status);
            log.debug("检查卧转立设备状态: taskId={}, deviceId={}, status={}, isCompleted={}",
                    taskId, transferDevice.getId(), status, isCompleted);
            return isCompleted;
        } catch (Exception e) {
            log.warn("检查卧转立设备状态失败: taskId={}", taskId, e);
            return false;
        }
    }
    private String determineOperation(DeviceConfig device, Map<String, Object> params) {
        if (params != null && params.containsKey("operation")) {
            Object op = params.get("operation");