cl
10 小时以前 43d6ae86bc229a1a75637fae33be378e105016e3
RCS通知
2个文件已添加
9个文件已修改
395 ■■■■■ 已修改文件
rsf-admin/src/page/task/TaskList.jsx 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/constant/RcsConstant.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/RcsBusTaskNoticeService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOutStockServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/RcsBusTaskNoticeServiceImpl.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReceiveMsgServiceImpl.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/TaskController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/TaskStsType.java 116 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/MaterialAutoSchedules.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/TaskService.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java 128 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/task/TaskList.jsx
@@ -372,6 +372,21 @@
    )
}
/** 拣料/盘点出库:仅 RCS 执行中(<198)可取消;199、198 不可取消 */
const canCancelPickOrCheckOut = (record) => {
    if (record?.taskType != 103 && record?.taskType != 107) return false;
    const s = record.taskStatus;
    return s < 198;
};
/** 普通入出库、空板、移库等:创建态可取消;199 不可取消 */
const canCancelLegacy = (record) => {
    const t = record?.taskType;
    const s = record?.taskStatus;
    if (t != 1 && t != 101 && t != 10 && t != 11) return false;
    return s == 1 || s == 101;
};
/**
 * 取消按钮
 * @returns 
@@ -393,8 +408,9 @@
            notify(msg);
        }
    }
    const showCancel = canCancelPickOrCheckOut(record) || canCancelLegacy(record);
    return (
        (record.taskStatus == 1 || record.taskStatus == 101 || record.taskStatus == 199) && (record.taskType == 1 || record.taskType == 101 || record.taskType == 10 || record.taskType == 107 || record.taskType == 103 || record.taskType == 11) ?
        showCancel ?
            <ConfirmButton
                onConfirm={clickCancel}
                startIcon={<CancelIcon />}
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/constant/RcsConstant.java
@@ -19,4 +19,7 @@
    
    //取消RCS任务
    public static String cancelTask = "/api/open/task/cancel";
    /** 任务状态反向通知 RCS(POST body: taskNo, status) */
    public static String TASK_STATUS_NOTICE = "/api/open/bus/notice";
}
rsf-server/src/main/java/com/vincent/rsf/server/api/service/RcsBusTaskNoticeService.java
New file
@@ -0,0 +1,14 @@
package com.vincent.rsf.server.api.service;
/**
 * 管理后台对工作档点「完成」时反向通知 RCS:POST /api/open/bus/notice。
 * 「取消」仅通过 RCS 取消接口(/api/open/task/cancel);PDA、RCS 回调、定时任务等不调此处。
 */
public interface RcsBusTaskNoticeService {
    /**
     * @param taskNo  工作档任务编码
     * @param status  {@link com.vincent.rsf.server.manager.enums.TaskStsType} 的 id(完成 98/198/200);取消走 RCS 取消接口,不用本接口
     */
    void notifyTaskStatus(String taskNo, Integer status);
}
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOutStockServiceImpl.java
@@ -191,7 +191,7 @@
            }
            if (first.getTaskType().equals(TaskType.TASK_TYPE_OUT.type)) {
                for (Task task : tasks) {
                    taskService.completeFullOutStock(task.getId(), loginUserId);
                    taskService.completeFullOutStock(task.getId(), loginUserId, false);
                }
                return R.ok("确认成功,全版出库已完成");
            }
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/RcsBusTaskNoticeServiceImpl.java
New file
@@ -0,0 +1,75 @@
package com.vincent.rsf.server.api.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.vincent.rsf.server.api.config.RemotesInfoProperties;
import com.vincent.rsf.server.api.entity.CommonResponse;
import com.vincent.rsf.server.api.entity.constant.RcsConstant;
import com.vincent.rsf.server.api.service.RcsBusTaskNoticeService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
/** 管理后台工作档完成/取消时通知 RCS */
@Slf4j
@Service
public class RcsBusTaskNoticeServiceImpl implements RcsBusTaskNoticeService {
    @Autowired(required = false)
    private RemotesInfoProperties.RcsApi rcsApi;
    @Autowired(required = false)
    private RestTemplate restTemplate;
    @Override
    public void notifyTaskStatus(String taskNo, Integer status) {
        if (StringUtils.isBlank(taskNo) || status == null) {
            return;
        }
        if (rcsApi == null || StringUtils.isBlank(rcsApi.getHost()) || StringUtils.isBlank(rcsApi.getPort())) {
            log.debug("跳过 RCS 任务状态通知:未配置 platform.rcs");
            return;
        }
        if (restTemplate == null) {
            log.warn("跳过 RCS 任务状态通知:RestTemplate 未注入");
            return;
        }
        String url = rcsApi.getHost() + ":" + rcsApi.getPort() + RcsConstant.TASK_STATUS_NOTICE;
        Map<String, String> body = new HashMap<>(2);
        body.put("taskNo", taskNo);
        body.put("status", String.valueOf(status));
        try {
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Type", "application/json");
            headers.add("api-version", "v2.0");
            HttpEntity<Map<String, String>> entity = new HttpEntity<>(body, headers);
            log.info("RCS 任务状态通知 POST {} body={}", url, JSONObject.toJSONString(body));
            ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
            if (exchange.getBody() == null) {
                log.warn("RCS 任务状态通知响应体为空 taskNo={}", taskNo);
                return;
            }
            ObjectMapper om = new ObjectMapper();
            om.coercionConfigDefaults().setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty);
            CommonResponse res = om.readValue(exchange.getBody(), CommonResponse.class);
            if (res.getCode() != null && res.getCode() == 200) {
                log.info("RCS 任务状态通知成功 taskNo={} status={}", taskNo, status);
            } else {
                log.warn("RCS 任务状态通知非成功 taskNo={} status={} code={} msg={}",
                        taskNo, status, res.getCode(), res.getMsg());
            }
        } catch (Exception e) {
            log.warn("RCS 任务状态通知异常 taskNo={} status={}:{}", taskNo, status, e.getMessage());
        }
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReceiveMsgServiceImpl.java
@@ -118,7 +118,34 @@
    private WarehouseAreasItemService warehouseAreasItemService;
    @Autowired
    private ConfigService configService;
    @Autowired
    private TaskItemService taskItemService;
    @Autowired
    private TaskService taskService;
    /**
     * 云仓改单/取消前:任务明细已关联该单据且主任务未逻辑删除则不允许
     */
    private void assertWkOrderNoLinkedTask(Long wkOrderId) {
        if (wkOrderId == null) {
            return;
        }
        List<TaskItem> links = taskItemService.list(new LambdaQueryWrapper<TaskItem>()
                .select(TaskItem::getTaskId)
                .and(w -> w.eq(TaskItem::getOrderId, wkOrderId)
                        .or(o -> o.eq(TaskItem::getSourceId, wkOrderId)
                                .eq(TaskItem::getOrderType, OrderType.ORDER_OUT.type))));
        if (links.isEmpty()) {
            return;
        }
        Set<Long> taskIds = links.stream().map(TaskItem::getTaskId).filter(Objects::nonNull).collect(Collectors.toSet());
        if (taskIds.isEmpty()) {
            return;
        }
        if (taskService.count(new LambdaQueryWrapper<Task>().in(Task::getId, taskIds)) > 0) {
            throw new CoolException("该单据已生成任务,不可修改、取消或删除!!");
        }
    }
    /**
     * @author Ryan
@@ -465,6 +492,7 @@
                    WkOrder order = asnOrderService.getOne(new LambdaQueryWrapper<WkOrder>()
                            .eq(WkOrder::getPoCode, syncOrder.getOrderInternalCode()));
                    if (!Objects.isNull(order)) {
                        assertWkOrderNoLinkedTask(order.getId());
                        // 仅未执行状态可被 order/add 修改(入库未执行、出库任务初始)
                        List<Short> editableStatus = Arrays.asList(AsnExceStatus.ASN_EXCE_STATUS_UN_EXCE.val
                                ,AsnExceStatus.ASN_EXCE_STATUS_UN_EXCE.val
@@ -943,6 +971,7 @@
            if (Objects.isNull(wkOrder)) {
                throw new CoolException("请确认单据:" + order.getOrderNo() + "是否已经执行或是否同步!!");
            }
            assertWkOrderNoLinkedTask(wkOrder.getId());
            order.getOrderItems().forEach(orderItem -> {
                WkOrderItem wkOrderItem = asnOrderItemService.getOne(new LambdaUpdateWrapper<WkOrderItem>()
                        .eq(WkOrderItem::getMatnrCode, orderItem.getMatnr())
@@ -1006,6 +1035,7 @@
                throw new CoolException("单据不存在,无法取消!!请提供单据内码(orderInternalCode)或单号(orderNo)。");
            }
            final WkOrder finalWkOrder = wkOrder;
            assertWkOrderNoLinkedTask(finalWkOrder.getId());
            // 已组托不可取消
            long pakinCount = waitPakinItemService.count(new LambdaQueryWrapper<WaitPakinItem>()
                    .eq(WaitPakinItem::getAsnId, finalWkOrder.getId()));
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/TaskController.java
@@ -138,7 +138,7 @@
        if (Cools.isEmpty(id)) {
            throw new CoolException("参数不能为空!!");
        }
        return R.ok("全版出库完结成功").add(taskService.completeFullOutStock(id, getLoginUserId()));
        return R.ok("全版出库完结成功").add(taskService.completeFullOutStock(id, getLoginUserId(), true));
    }
    /**
rsf-server/src/main/java/com/vincent/rsf/server/manager/enums/TaskStsType.java
@@ -1,58 +1,58 @@
package com.vincent.rsf.server.manager.enums;
public enum TaskStsType {
    //任务状态
    GENERATE_IN("1", "创建入库任务"),
    WCS_EXECUTE_IN("2", "RCS任务已下发"),
    WCS_CONTAINER_RECEIVE("3", "RCS容器到达"),
    WCS_CONVEYOR_START("4", "RCS容器流动任务已下发"),
    WCS_TOTE_LOAD("5", "RCS取箱完成"),
    WCS_TOTE_UNLOAD("6", "RCS放箱完成"),
    WCS_PUTAWAY_SUCESS("7", "RCS任务完成"),
//    WCS_PUTAWAY_FAILED(11L, "任务失败"),
//
//    WCS_PUTAWAY_CANCEL(12L, "任务取消"),
//
//    WCS_PUTAWAY_SUSPEND(13L, "入库任务挂起"),
    COMPLETE_IN("98", "入库完成"),
    REPORT_IN("99", "上报完成"),
    UPDATED_IN("100", "库存更新完成"),
    GENERATE_OUT("101", "创建出库任务"),
    WCS_EXECUTE_OUT("102", "RCS出库任务已下发"),
    WCS_EXECUTE_OUT_TOTE_LOAD("103", "RCS取箱完成"),
    WCS_EXECUTE_OUT_TOTE_UNLOAD("104", "RCS放箱完成"),
    WCS_EXECUTE_OUT_TASK_DONE("105", "RCS任务完成"),
    WCS_EXECUTE_OUT_ARRIVED("106", "RCS容器已到达"),
    WCS_EXECUTE_OUT_CONVEYOR("107", "RCS容器流动任务已下发"),
    AWAIT("196","等待确认"),
    GENERATE_WAVE_SEED("197", "等待容器到达"),
    COMPLETE_OUT("198", "出库完成"),
    WAVE_SEED("199", "播种中/盘点中/待确认"),
    UPDATED_OUT("200", "库存更新完成"),
    ;
    public Integer id;
    public String desc;
    TaskStsType(String id, String desc) {
        this.id = Integer.parseInt(id);
        this.desc = desc;
    }
}
package com.vincent.rsf.server.manager.enums;
public enum TaskStsType {
    //任务状态
    GENERATE_IN("1", "创建入库任务"),
    WCS_EXECUTE_IN("2", "RCS任务已下发"),
    WCS_CONTAINER_RECEIVE("3", "RCS容器到达"),
    WCS_CONVEYOR_START("4", "RCS容器流动任务已下发"),
    WCS_TOTE_LOAD("5", "RCS取箱完成"),
    WCS_TOTE_UNLOAD("6", "RCS放箱完成"),
    WCS_PUTAWAY_SUCESS("7", "RCS任务完成"),
//    WCS_PUTAWAY_FAILED(11L, "任务失败"),
//
//    WCS_PUTAWAY_CANCEL(12L, "任务取消"),
//
//    WCS_PUTAWAY_SUSPEND(13L, "入库任务挂起"),
    COMPLETE_IN("98", "入库完成"),
    REPORT_IN("99", "上报完成"),
    UPDATED_IN("100", "库存更新完成"),
    GENERATE_OUT("101", "创建出库任务"),
    WCS_EXECUTE_OUT("102", "RCS出库任务已下发"),
    WCS_EXECUTE_OUT_TOTE_LOAD("103", "RCS取箱完成"),
    WCS_EXECUTE_OUT_TOTE_UNLOAD("104", "RCS放箱完成"),
    WCS_EXECUTE_OUT_TASK_DONE("105", "RCS任务完成"),
    WCS_EXECUTE_OUT_ARRIVED("106", "RCS容器已到达"),
    WCS_EXECUTE_OUT_CONVEYOR("107", "RCS容器流动任务已下发"),
    AWAIT("196","等待确认"),
    GENERATE_WAVE_SEED("197", "等待容器到达"),
    COMPLETE_OUT("198", "出库完成"),
    WAVE_SEED("199", "播种中/盘点中/待确认"),
    UPDATED_OUT("200", "库存更新完成"),
    ;
    public Integer id;
    public String desc;
    TaskStsType(String id, String desc) {
        this.id = Integer.parseInt(id);
        this.desc = desc;
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/MaterialAutoSchedules.java
@@ -307,7 +307,7 @@
                continue;
            }
            try {
                taskService.completeFullOutStock(task.getId(), SYSTEM_USER_ID);
                taskService.completeFullOutStock(task.getId(), SYSTEM_USER_ID, false);
                log.info("[自动拣货完成] 任务: {}, 料箱: {}, 物料: {} 已自动确认出库并更新库存", task.getTaskCode(), task.getBarcode(), matnrCode);
            } catch (Exception e) {
                log.warn("[自动拣货完成] 任务: {} 处理失败: {}", task.getTaskCode(), e.getMessage());
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/TaskService.java
@@ -28,7 +28,10 @@
    Task operateComplete(Long id, Long loginUserId);
    Task completeFullOutStock(Long id, Long loginUserId);
    /**
     * @param notifyRcsFromAdmin true:管理后台「全版出库完结」接口调用时通知 RCS;false:定时/PDA 等同源不通知
     */
    Task completeFullOutStock(Long id, Long loginUserId, boolean notifyRcsFromAdmin);
    void moveToDeep(Long loginUserId, String curLoc) throws Exception;
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
@@ -13,6 +13,7 @@
import com.vincent.rsf.server.api.config.RemotesInfoProperties;
import com.vincent.rsf.server.api.controller.erp.params.InOutResultReportParam;
import com.vincent.rsf.server.api.controller.erp.params.TaskInParam;
import com.vincent.rsf.server.api.service.RcsBusTaskNoticeService;
import com.vincent.rsf.server.api.entity.CommonResponse;
import com.vincent.rsf.server.api.entity.constant.RcsConstant;
import com.vincent.rsf.server.api.entity.dto.InTaskMsgDto;
@@ -131,6 +132,8 @@
    private CloudWmsNotifyLogService cloudWmsNotifyLogService;
    @Autowired
    private WarehouseService warehouseService;
    @Autowired
    private RcsBusTaskNoticeService rcsBusTaskNoticeService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -509,7 +512,7 @@
    }
    /**
     * 手动完成任务
     * 手动完成任务:入库类置 98、出库类置 198,库位/单据扣减与上报由对应定时任务执行
     *
     * @param id
     * @param loginUserId
@@ -549,13 +552,22 @@
        }
        modiftyTaskSort(task, loginUserId);
        // 如果任务状态已经是AWAIT (196),再次点击完结时,直接完成
        if (task.getTaskStatus().equals(TaskStsType.AWAIT.id)) {
            // AWAIT状态的任务再次完结,直接设置为出库完成
        // 入库:->98,出库:-> 198,由 complateOutStock 定时任务 更新库存
        if (task.getTaskType() < 100) {
            task.setTaskStatus(TaskStsType.COMPLETE_IN.id);
            if (StringUtils.isNotBlank(task.getOrgSite())) {
                BasStation station = basStationService.getOne(new LambdaQueryWrapper<BasStation>()
                        .eq(BasStation::getStationName, task.getOrgSite()));
                if (Objects.nonNull(station) && station.getType().equals(StationTypeEnum.STATION_TYPE_NORMAL.type)) {
                    station.setUseStatus(LocStsType.LOC_STS_TYPE_O.type);
                    if (!basStationService.updateById(station)) {
                        throw new CoolException("入库站点状态修改失败!!");
                    }
                }
            }
        } else {
            task.setTaskStatus(TaskStsType.COMPLETE_OUT.id);
            // 更新出库站点状态(与RCS通知完结保持一致)
            if (task.getTaskType() >= TaskType.TASK_TYPE_OUT.type && StringUtils.isNotBlank(task.getTargSite())) {
                BasStation station = basStationService.getOne(new LambdaQueryWrapper<BasStation>()
                        .eq(BasStation::getStationName, task.getTargSite()));
@@ -566,28 +578,17 @@
                    }
                }
            }
        } else {
            // 其他情况按原有逻辑处理
            // 入库任务(taskType < 100):设置为入库完成
            // 出库任务(taskType >= 100):设置为等待确认
            Integer newStatus = task.getTaskType() < 100 ? TaskStsType.COMPLETE_IN.id : TaskStsType.AWAIT.id;
            task.setTaskStatus(newStatus);
            // 如果是入库任务完成,更新入库站点状态(与RCS通知完结保持一致)
            if (newStatus.equals(TaskStsType.COMPLETE_IN.id) && StringUtils.isNotBlank(task.getOrgSite())) {
                BasStation station = basStationService.getOne(new LambdaQueryWrapper<BasStation>()
                        .eq(BasStation::getStationName, task.getOrgSite()));
                if (Objects.nonNull(station) && station.getType().equals(StationTypeEnum.STATION_TYPE_NORMAL.type)) {
                    station.setUseStatus(LocStsType.LOC_STS_TYPE_O.type);
                    if (!basStationService.updateById(station)) {
                        throw new CoolException("入库站点状态修改失败!!");
                    }
                }
            }
        }
        // 原:196 时再点一次才置 198;出库首次点击曾置 196(AWAIT)
        // if (task.getTaskStatus().equals(TaskStsType.AWAIT.id)) { task.setTaskStatus(TaskStsType.COMPLETE_OUT.id); ... }
        // else { Integer newStatus = task.getTaskType() < 100 ? COMPLETE_IN.id : AWAIT.id; ... }
        
        if (!this.updateById(task)) {
            throw new CoolException("完成任务失败");
        }
        // 管理后台「完成任务」通知 RCS
        if (StringUtils.isNotBlank(task.getTaskCode())) {
            rcsBusTaskNoticeService.notifyTaskStatus(task.getTaskCode(), task.getTaskStatus());
        }
        return task;
    }
@@ -597,11 +598,12 @@
     *
     * @param id 任务ID
     * @param loginUserId 登录用户ID
     * @param notifyRcsFromAdmin 管理后台全版出库完结接口为 true 时通知 RCS;定时/PDA 等为 false
     * @return 任务对象
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Task completeFullOutStock(Long id, Long loginUserId) {
    public Task completeFullOutStock(Long id, Long loginUserId, boolean notifyRcsFromAdmin) {
        // 查询任务
        Task task = taskService.getOne(new LambdaQueryWrapper<Task>()
                .eq(Task::getId, id));
@@ -667,6 +669,9 @@
        if (!this.updateById(task)) {
            throw new CoolException("任务状态更新失败!!");
        }
        if (notifyRcsFromAdmin && StringUtils.isNotBlank(task.getTaskCode())) {
            rcsBusTaskNoticeService.notifyTaskStatus(task.getTaskCode(), TaskStsType.UPDATED_OUT.id);
        }
        return task;
@@ -984,59 +989,56 @@
            }
        }
        
        // 如果有任务已下发到RCS,先调用RCS取消接口
        // 已下发 RCS 的工作档:必须先调 RCS 取消接口成功,否则不允许取消工作档
        boolean rcsCancelSuccess = false;
        if (!rcsTaskCodes.isEmpty()) {
            // 检查 RCS API 配置是否有效
            if (rcsApi == null || StringUtils.isBlank(rcsApi.getHost()) || StringUtils.isBlank(rcsApi.getPort())) {
                log.error("========== RCS任务取消失败 ==========");
                log.error("RCS API 配置无效!host: {}, port: {}",
                        rcsApi != null ? rcsApi.getHost() : "null",
                        rcsApi != null ? rcsApi.getPort() : "null");
                // 即使配置无效,也继续执行任务删除操作
            } else {
                try {
                    log.info("========== 开始取消RCS任务 ==========");
                    log.info("需要取消的RCS任务编号:{}", rcsTaskCodes);
                    String rcsUrl = rcsApi.getHost() + ":" + rcsApi.getPort() + RcsConstant.cancelTask;
                    log.info("RCS取消任务请求地址:{}", rcsUrl);
                // 如果没有批次编号,使用第一个任务编号作为批次编号
                if (StringUtils.isBlank(batchNo) && !rcsTaskCodes.isEmpty()) {
                throw new CoolException("任务已下发RCS,但未配置RCS地址,无法取消!!");
            }
            if (restTemplate == null) {
                throw new CoolException("任务已下发RCS,但无法调用RCS取消接口,无法取消!!");
            }
            try {
                log.info("========== 开始取消RCS任务 ==========");
                log.info("需要取消的RCS任务编号:{}", rcsTaskCodes);
                String rcsUrl = rcsApi.getHost() + ":" + rcsApi.getPort() + RcsConstant.cancelTask;
                log.info("RCS取消任务请求地址:{}", rcsUrl);
                if (StringUtils.isBlank(batchNo)) {
                    batchNo = rcsTaskCodes.get(0);
                }
                Map<String, Object> cancelParams = new HashMap<>();
                cancelParams.put("tasks", rcsTaskCodes);
                if (StringUtils.isNotBlank(batchNo)) {
                    cancelParams.put("batchNo", batchNo);
                }
                log.info("RCS取消任务请求参数:{}", JSONObject.toJSONString(cancelParams));
                HttpHeaders headers = new HttpHeaders();
                headers.add("Content-Type", "application/json");
                headers.add("api-version", "v2.0");
                HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<>(cancelParams, headers);
                long startTime = System.currentTimeMillis();
                ResponseEntity<String> exchange = restTemplate.exchange(rcsUrl, HttpMethod.POST, httpEntity, String.class);
                long endTime = System.currentTimeMillis();
                log.info("RCS取消任务响应耗时:{}ms", (endTime - startTime));
                log.info("RCS取消任务响应状态码:{}", exchange.getStatusCode());
                log.info("RCS取消任务响应体:{}", exchange.getBody());
                if (Objects.isNull(exchange.getBody())) {
                    log.error("RCS取消任务失败:响应体为空");
                    throw new CoolException("RCS取消任务失败:响应体为空");
                }
                ObjectMapper objectMapper = new ObjectMapper();
                objectMapper.coercionConfigDefaults()
                        .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty);
                CommonResponse result = objectMapper.readValue(exchange.getBody(), CommonResponse.class);
                if (result.getCode() == 200) {
                    log.info("========== RCS任务取消成功 ==========");
                    log.info("成功取消的RCS任务编号:{}", rcsTaskCodes);
@@ -1045,22 +1047,28 @@
                    log.error("RCS取消任务失败:{}", result.getMsg());
                    throw new CoolException("RCS取消任务失败:" + result.getMsg());
                }
                } catch (JsonProcessingException e) {
                    log.error("RCS取消任务响应解析失败:{}", e.getMessage(), e);
                    throw new CoolException("RCS取消任务响应解析失败:" + e.getMessage());
                } catch (Exception e) {
                    log.error("RCS取消任务异常:{}", e.getMessage(), e);
                    throw new CoolException("RCS取消任务异常:" + e.getMessage());
                }
            } catch (CoolException e) {
                throw e;
            } catch (JsonProcessingException e) {
                log.error("RCS取消任务响应解析失败:{}", e.getMessage(), e);
                throw new CoolException("RCS取消任务响应解析失败:" + e.getMessage());
            } catch (Exception e) {
                log.error("RCS取消任务异常:{}", e.getMessage(), e);
                throw new CoolException("RCS取消任务异常:" + e.getMessage());
            }
        }
        
        // 查询符合取消条件的任务(状态为1、101、199)
        List<Integer> allowedStatuses = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id, TaskStsType.WAVE_SEED.id);
        // 可取消状态:原 1/101(不含 199);拣料/盘点出库 RCS 执行中(<198);拣料/盘点再入库(53/57)不支持取消
        List<Task> tasks = this.list(new LambdaQueryWrapper<Task>()
                .in(Task::getTaskType, list)
                .in(Task::getId, (Object[]) ids)
                .in(Task::getTaskStatus, allowedStatuses));
                .and(w -> w
                        .in(Task::getTaskStatus, Arrays.asList(
                                TaskStsType.GENERATE_IN.id,
                                TaskStsType.GENERATE_OUT.id))
                        .or(w2 -> w2
                                .in(Task::getTaskType, TaskType.TASK_TYPE_PICK_AGAIN_OUT.type, TaskType.TASK_TYPE_CHECK_OUT.type)
                                .lt(Task::getTaskStatus, TaskStsType.COMPLETE_OUT.id))));
        
        // 如果符合取消条件的任务为空,但RCS取消成功,允许继续(可能是任务状态已变更)
        if (tasks.isEmpty() && !rcsCancelSuccess) {