chen.lin
昨天 e9543f18fbe81fb492df941fdc6fce59424f8f49
RCS对接
1个文件已添加
14个文件已修改
834 ■■■■■ 已修改文件
rsf-admin/src/page/task/TaskList.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/WmsRcsController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/constant/RcsConstant.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/params/RcsPubTaskParams.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/params/TaskReportParams.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/WmsRcsService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/WmsRcsServiceImpl.java 192 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/constant/RcsConstant.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/WcsTaskParams.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java 135 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java 66 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java 279 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaitPakinServiceImpl.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/task/TaskList.jsx
@@ -357,7 +357,7 @@
        }
    }
    return (
        (record.taskStatus == 1 || record.taskStatus == 101) && (record.taskType == 1 || record.taskType == 101 || record.taskType == 10 || record.taskType == 107 || record.taskType == 103 || record.taskType == 11) ?
        (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) ?
            <ConfirmButton
                onConfirm={clickCancel}
                startIcon={<CancelIcon />}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/WmsRcsController.java
@@ -7,9 +7,11 @@
import com.vincent.rsf.openApi.entity.params.LocSiteParams;
import com.vincent.rsf.openApi.entity.params.RcsPubTaskParams;
import com.vincent.rsf.openApi.entity.params.SyncRcsLocsParam;
import com.vincent.rsf.openApi.entity.params.TaskReportParams;
import com.vincent.rsf.openApi.service.WmsRcsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -19,6 +21,7 @@
import java.util.Map;
import java.util.Objects;
@Slf4j
@RestController
@Api("RCS调度交互接口")
@RequestMapping("/rcs")
@@ -97,5 +100,20 @@
        return wmsRcsService.modifyLocOrSite(params);
    }
    /**
     * @author Ryan
     * @date 2026/2/3
     * @description: RCS回调接口
     * @version 1.0
     */
    @ApiOperation("RCS回调接口")
    @PostMapping("/api/open/task/report")
    public CommonResponse reportTask(@RequestBody TaskReportParams params) {
        log.debug("RCS回调:{}", params);
        if (Objects.isNull(params)) {
            throw new CoolException("参数不能为空!!");
        }
        return wmsRcsService.reportTask(params);
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/constant/RcsConstant.java
@@ -8,4 +8,6 @@
    public static String modifystatus = "/api/open/modify/status";
    public static String cancelTask = "/api/open/task/cancel";
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/params/RcsPubTaskParams.java
@@ -14,10 +14,10 @@
public class RcsPubTaskParams implements Serializable {
    @ApiModelProperty("批次")
    private String batch;
    private String batchNo;
    @ApiModelProperty("任务明细")
    private List<TaskParam> taskList;
    private List<TaskParam> tasks;
}
@Data
@@ -25,11 +25,8 @@
@ApiModel(value = "TaskItem", description = "任务列表")
class TaskParam {
    @ApiModelProperty("任务类型{LOC_TO_LOC: 移库, LOC_TO_STA: 出库, STA_TO_LOC: 入库, STA_TO_STA: 站点间搬运}")
    private String taskType;
    @ApiModelProperty("任务号")
    private String seqNum;
    private String taskNo;
    @ApiModelProperty("起始库位")
    private String oriLoc;
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/params/TaskReportParams.java
New file
@@ -0,0 +1,29 @@
package com.vincent.rsf.openApi.entity.params;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
 * @author Ryan
 * @date 2026/2/3
 * @description: RCS回调
 * @version 1.0
 */
@Data
@Accessors(chain = true)
@ApiModel(value = "TaskReportParams", description = "RCS回调")
public class TaskReportParams implements Serializable {
    @ApiModelProperty(value = "任务批次", required = true)
    private String batchNo;
    @ApiModelProperty(value = "任务编号", required = true)
    private String taskNo;
    @ApiModelProperty(value = "时间戳", required = true)
    private String timestamp;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/WmsRcsService.java
@@ -10,6 +10,7 @@
import com.vincent.rsf.openApi.entity.params.LocSiteParams;
import com.vincent.rsf.openApi.entity.params.RcsPubTaskParams;
import com.vincent.rsf.openApi.entity.params.SyncRcsLocsParam;
import com.vincent.rsf.openApi.entity.params.TaskReportParams;
import java.util.List;
import java.util.Map;
@@ -25,4 +26,6 @@
    List<SyncLocsDto> syncLocs(SyncRcsLocsParam params);
    R modifyLocOrSite(LocSiteParams params);
    CommonResponse reportTask(TaskReportParams params);
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/WmsRcsServiceImpl.java
@@ -19,6 +19,7 @@
import com.vincent.rsf.openApi.entity.params.LocSiteParams;
import com.vincent.rsf.openApi.entity.params.RcsPubTaskParams;
import com.vincent.rsf.openApi.entity.params.SyncRcsLocsParam;
import com.vincent.rsf.openApi.entity.params.TaskReportParams;
import com.vincent.rsf.openApi.mapper.LocMapper;
import com.vincent.rsf.openApi.service.WmsRcsService;
import lombok.extern.slf4j.Slf4j;
@@ -60,28 +61,57 @@
    public CommonResponse pubTasks(RcsPubTaskParams params)  {
        /**RCS基础配置链接*/
        String rcsUrl =  rcsApi.getHost() + ":" + rcsApi.getPort() + RcsConstant.pubTask;
        log.info("任务下发,请求地址: {}, 请求参数: {}", rcsUrl , JSONObject.toJSONString(params));
        log.info("========== 开始下发任务到RCS ==========");
        log.info("RCS请求地址:{}", rcsUrl);
        if (params != null) {
            log.info("批次编号:{}", params.getBatchNo());
            if (params.getTasks() != null) {
                log.info("任务数量:{}", params.getTasks().size());
            }
        }
        log.info("请求参数:{}", JSONObject.toJSONString(params));
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        headers.add("api-version", "v2.0");
        HttpEntity httpEntity = new HttpEntity(params, headers);
        long startTime = System.currentTimeMillis();
        ResponseEntity<String> exchange = restTemplate.exchange(rcsUrl, HttpMethod.POST, httpEntity, String.class);
        log.info("任务下发后,响应结果: {}", exchange);
        long endTime = System.currentTimeMillis();
        log.info("RCS响应耗时:{}ms", (endTime - startTime));
        log.info("RCS响应状态码:{}", exchange.getStatusCode());
        log.info("RCS响应头:{}", exchange.getHeaders());
        log.info("RCS响应体:{}", exchange.getBody());
        if (Objects.isNull(exchange.getBody())) {
            throw new CoolException("任务下发失败!!");
            log.error("========== RCS任务下发失败 ==========");
            log.error("RCS响应体为空,无法解析响应结果");
            log.error("请求地址:{}", rcsUrl);
            log.error("请求参数:{}", JSONObject.toJSONString(params));
            throw new CoolException("任务下发失败,RCS响应体为空!!");
        } else {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.coercionConfigDefaults()
                    .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty);
            try {
                CommonResponse result = objectMapper.readValue(exchange.getBody(), CommonResponse.class);
                log.info("RCS响应解析结果 - code:{},msg:{},data:{}",
                        result.getCode(), result.getMsg(), result.getData());
                if (result.getCode() == 200) {
                    log.info("========== RCS任务下发成功 ==========");
                    return result;
                } else {
                    log.error("========== RCS任务下发失败 ==========");
                    log.error("RCS返回错误 - code:{},msg:{},data:{}",
                            result.getCode(), result.getMsg(), result.getData());
                    return result;
                }
            } catch (JsonProcessingException e) {
                throw new CoolException(e.getMessage());
                log.error("========== RCS任务下发异常 ==========");
                log.error("解析RCS响应失败,响应体:{}", exchange.getBody(), e);
                throw new CoolException("解析RCS响应失败:" + e.getMessage());
            } catch (Exception e) {
                log.error("========== RCS任务下发异常 ==========");
                log.error("任务下发过程中发生异常", e);
                throw e;
            }
        }
    }
@@ -93,8 +123,66 @@
     * @version 1.0
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResponse cancelTasks(Map<String, Object> params) {
        return  new CommonResponse();
        /**RCS基础配置链接*/
        String rcsUrl = rcsApi.getHost() + ":" + rcsApi.getPort() + RcsConstant.cancelTask;
        log.info("========== 开始取消RCS任务(open-rcs接口) ==========");
        log.info("RCS取消任务请求地址:{}", rcsUrl);
        log.info("RCS取消任务请求参数:{}", JSONObject.toJSONString(params));
        // 打印详细的请求参数信息
        if (params != null) {
            Object batchNo = params.get("batchNo");
            Object tasks = params.get("tasks");
            if (batchNo != null) {
                log.info("批次编号:{}", batchNo);
            }
            if (tasks != null) {
                if (tasks instanceof List) {
                    log.info("任务数量:{}", ((List<?>) tasks).size());
                    log.info("任务编号列表:{}", tasks);
                } else {
                    log.info("任务参数:{}", tasks);
                }
            }
        }
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        headers.add("api-version", "v2.0");
        HttpEntity httpEntity = new HttpEntity(params, 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.getHeaders());
        log.info("RCS取消任务响应体:{}", exchange.getBody());
        if (Objects.isNull(exchange.getBody())) {
            throw new CoolException("取消任务失败!!");
        } else {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.coercionConfigDefaults()
                    .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty);
            try {
                CommonResponse result = objectMapper.readValue(exchange.getBody(), CommonResponse.class);
                if (result.getCode() == 200) {
                    log.info("========== RCS任务取消成功(open-rcs接口) ==========");
                    log.info("成功取消的任务编号:{}", params.get("tasks"));
                    return result;
                } else {
                    log.error("========== RCS任务取消失败(open-rcs接口) ==========");
                    log.error("RCS返回错误码:{},错误信息:{}", result.getCode(), result.getMsg());
                    throw new CoolException("取消任务失败!!" + (result.getMsg() != null ? ":" + result.getMsg() : ""));
                }
            } catch (JsonProcessingException e) {
                log.error("RCS取消任务响应解析失败:{}", e.getMessage(), e);
                throw new CoolException("RCS取消任务响应解析失败:" + e.getMessage());
            }
        }
    }
    /**
@@ -105,6 +193,24 @@
     */
    @Override
    public CommonResponse callBackEvent(ExMsgCallbackParams params) {
        // 参数校验
        if (Objects.isNull(params)) {
            log.error("RCS回调事件参数为空!");
            throw new CoolException("参数不能为空!!");
        }
        // 详细记录接收到的参数
        log.info("RCS回调事件接收参数 - seqNum: {}, eventType: {}, robotCode: {}, zpallet: {}",
                params.getSeqNum(), params.getEventType(), params.getRobotCode(), params.getZpallet());
        // 检查关键字段是否为空
        if (Objects.isNull(params.getSeqNum()) || params.getSeqNum().isEmpty()) {
            log.warn("RCS回调事件参数seqNum为空!完整参数:{}", JSONObject.toJSONString(params));
        }
        if (Objects.isNull(params.getEventType()) || params.getEventType().isEmpty()) {
            log.warn("RCS回调事件参数eventType为空!完整参数:{}", JSONObject.toJSONString(params));
        }
        String callUrl =  wmsApi.getHost() + ":" + wmsApi.getPort() + WmsConstant.callBack;
        /**WMS基础配置链接*/
        log.info("任务执行状态上报,请求地址: {}, 请求参数: {}", callUrl , JSONObject.toJSONString(params));
@@ -192,7 +298,7 @@
    public List<SyncLocsDto> syncRcsLocs(SyncRcsLocsParam  params) {
        /**RCS基础配置链接*/
        String rcsUrl =  rcsApi.getHost() + ":" + rcsApi.getPort() + RcsConstant.syncLocs;
        log.info("任务下发,请求地址: {}, 请求参数: {}", rcsUrl , JSONObject.toJSONString(params));
        log.info("任务下发,请求地址2: {}, 请求参数: {}", rcsUrl , JSONObject.toJSONString(params));
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        headers.add("api-version", "v2.0");
@@ -220,4 +326,78 @@
            }
        }
    }
    /**
     * @author Ryan
     * @date 2026/2/3
     * @description: 任务执行通知上报(RCS回调接口)
     * @version 1.0
     */
    @Override
    public CommonResponse reportTask(TaskReportParams params) {
        log.info("RCS回调,请求参数: {}", JSONObject.toJSONString(params));
        // 参数校验
        if (Objects.isNull(params)) {
            throw new CoolException("参数不能为空!!");
        }
        if (Objects.isNull(params.getBatchNo()) || params.getBatchNo().isEmpty()) {
            throw new CoolException("任务批次不能为空!!");
        }
        if (Objects.isNull(params.getTaskNo()) || params.getTaskNo().isEmpty()) {
            throw new CoolException("任务编号不能为空!!");
        }
        if (Objects.isNull(params.getTimestamp()) || params.getTimestamp().isEmpty()) {
            throw new CoolException("时间戳不能为空!!");
        }
        // 将TaskReportParams转换为ExMsgParams格式(taskNo -> seqNum)
        // 根据RCS新接口规范,taskNo对应旧接口的seqNum
        JSONObject exMsgParams = new JSONObject();
        exMsgParams.put("seqNum", params.getTaskNo()); // taskNo映射到seqNum
        // eventType设置为END,表示任务完成(根据业务需求可能需要调整)
        exMsgParams.put("eventType", "END");
        exMsgParams.put("robotCode", null);
        exMsgParams.put("zpallet", null);
        // 将任务上报回调转发到WMS系统
        String callUrl = wmsApi.getHost() + ":" + wmsApi.getPort() + WmsConstant.callBack;
        log.info("RCS回调,请求地址: {}, 转换后参数: {}", callUrl, exMsgParams.toJSONString());
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        headers.add("api-version", "v2.0");
        HttpEntity httpEntity = new HttpEntity(exMsgParams, headers);
        ResponseEntity<String> exchange = restTemplate.exchange(callUrl, HttpMethod.POST, httpEntity, String.class);
        log.info("RCS回调,响应结果: {}", exchange);
        if (Objects.isNull(exchange.getBody())) {
            // 如果回调失败,返回成功响应(避免RCS重复回调)
            CommonResponse response = new CommonResponse();
            response.setCode(200);
            response.setMsg("接收成功");
            log.warn("RCS回调失败,但返回成功响应,任务编号:{},批次:{}", params.getTaskNo(), params.getBatchNo());
            return response;
        } else {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.coercionConfigDefaults()
                    .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty);
            try {
                CommonResponse result = objectMapper.readValue(exchange.getBody(), CommonResponse.class);
                if (result.getCode() == 200) {
                    log.info("RCS回调上报成功,任务编号:{},批次:{}", params.getTaskNo(), params.getBatchNo());
                    return result;
                } else {
                    log.warn("RCS回调上报回调返回非200状态,任务编号:{},批次:{},响应:{}", params.getTaskNo(), params.getBatchNo(), exchange.getBody());
                    return result;
                }
            } catch (JsonProcessingException e) {
                log.error("RCS回调上报回调响应解析失败,任务编号:{},批次:{},错误:{}", params.getTaskNo(), params.getBatchNo(), e.getMessage());
                // 解析失败时返回成功响应,避免RCS重复回调
                CommonResponse response = new CommonResponse();
                response.setCode(200);
                response.setMsg("接收成功");
                return response;
            }
        }
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/constant/RcsConstant.java
@@ -2,8 +2,8 @@
public class RcsConstant {
    //下发任务至RCS
    public static String pubTask = "/rsf-open-api/rcs/pub/task";
    //下发任务至RCS(新接口路径)
    public static String pubTask = "/api/open/bus/submit";
    //同步RCS库位信息
    public static String syncLocs = "/rsf-open-api/rcs/sync/locs";
@@ -16,4 +16,7 @@
    //上报站点状态
    public static String REPORT_SITE_STATUS = "/rsf-open-api/rcs/modify/status";
    //取消RCS任务
    public static String cancelTask = "/api/open/task/cancel";
}
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/WcsTaskParams.java
@@ -14,9 +14,9 @@
@ApiModel(value = "WcsTaskParams", description = "任务下发参数")
public class WcsTaskParams implements Serializable {
    @ApiModelProperty("批次")
    private String batch;
    @ApiModelProperty("批次编号")
    private String batchNo;
    @ApiModelProperty("任务明细")
    private List<TaskItemParam> taskList;
    @ApiModelProperty("任务数组")
    private List<TaskItemParam> tasks;
}
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java
@@ -35,6 +35,8 @@
import com.vincent.rsf.server.system.service.UserService;
import com.vincent.rsf.server.system.utils.SerialRuleUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -57,6 +59,8 @@
 */
@Service
public class MobileServiceImpl implements MobileService {
    private static final Logger logger = LoggerFactory.getLogger(MobileServiceImpl.class);
    @Value("${super.pwd}")
    private String superPwd;
@@ -544,9 +548,9 @@
        String batch = params.get("batch");
//        String barcode = params.get("barcode");
        if (Objects.isNull(crushNo)) {
            throw new CoolException("票号不能为空!!");
        }
//        if (Objects.isNull(crushNo)) {
//            throw new CoolException("票号不能为空!!");
//        }
//        if (Objects.isNull(code)) {
//            throw new CoolException("容器号不能为空!!");
//        }
@@ -561,29 +565,132 @@
//            }
//        }
        String fieldIndex = null;
        String fieldIndexTemp = null;
        if (!Objects.isNull(crushNo)) {
            FieldsItem fieldsItem = fieldsItemService.getOne(new LambdaQueryWrapper<FieldsItem>().eq(FieldsItem::getValue, crushNo).last("Limit 1"));
            if (!Objects.isNull(fieldsItem)) {
                fieldIndex = fieldsItem.getUuid();
                fieldIndexTemp = fieldsItem.getUuid();
            }
        }
        final String fieldIndex = fieldIndexTemp;
        // 检查是否有至少一个有效查询条件
        boolean hasValidCondition = !Cools.isEmpty(code) || !Cools.isEmpty(batch)
                || !Objects.isNull(fieldIndex) || !Cools.isEmpty(matnrCode) || !Cools.isEmpty(asnCode);
        if (!hasValidCondition) {
            throw new CoolException("请至少输入一个查询条件:物料编码、ASN单号、跟踪码、批次或票号");
        }
        //TODO 后续需根据策略配置,获取组拖数据。如:混装,按批次混装等
        LambdaQueryWrapper<WarehouseAreasItem> queryWrapper = new LambdaQueryWrapper<WarehouseAreasItem>()
                .or().eq(!Cools.isEmpty(code), WarehouseAreasItem::getTrackCode, code)
                .or().eq(!Cools.isEmpty(batch), WarehouseAreasItem::getSplrBatch, batch)
                .or().eq(WarehouseAreasItem::getFieldsIndex, fieldIndex)
                .or().eq(!Cools.isEmpty(matnrCode), WarehouseAreasItem::getMatnrCode, matnrCode)
                .or().eq(!Cools.isEmpty(asnCode), WarehouseAreasItem::getAsnCode, asnCode);
        List<WarehouseAreasItem> list = warehouseAreasItemService.list(queryWrapper);
        if (!list.isEmpty()) {
            list.removeIf(e -> e.getAnfme() <= e.getWorkQty());
        LambdaQueryWrapper<WarehouseAreasItem> queryWrapper = new LambdaQueryWrapper<>();
        // 构建OR查询条件组
        // 统计有效条件数量
        int conditionCount = 0;
        if (!Cools.isEmpty(code)) conditionCount++;
        if (!Cools.isEmpty(batch)) conditionCount++;
        if (!Objects.isNull(fieldIndex)) conditionCount++;
        if (!Cools.isEmpty(matnrCode)) conditionCount++;
        if (!Cools.isEmpty(asnCode)) conditionCount++;
        // 如果只有一个条件,直接使用eq;如果有多个条件,使用and包裹or条件组
        if (conditionCount == 1) {
            // 单个条件,直接查询
            if (!Cools.isEmpty(code)) {
                queryWrapper.eq(WarehouseAreasItem::getTrackCode, code);
            } else if (!Cools.isEmpty(batch)) {
                queryWrapper.eq(WarehouseAreasItem::getSplrBatch, batch);
            } else if (!Objects.isNull(fieldIndex)) {
                queryWrapper.eq(WarehouseAreasItem::getFieldsIndex, fieldIndex);
            } else if (!Cools.isEmpty(matnrCode)) {
                queryWrapper.eq(WarehouseAreasItem::getMatnrCode, matnrCode);
            } else if (!Cools.isEmpty(asnCode)) {
                queryWrapper.eq(WarehouseAreasItem::getAsnCode, asnCode);
        }
        } else {
            // 多个条件,使用OR连接
            queryWrapper.and(wrapper -> {
                boolean first = true;
                if (!Cools.isEmpty(code)) {
                    wrapper.eq(WarehouseAreasItem::getTrackCode, code);
                    first = false;
                }
                if (!Cools.isEmpty(batch)) {
                    if (!first) wrapper.or();
                    wrapper.eq(WarehouseAreasItem::getSplrBatch, batch);
                    first = false;
                }
                if (!Objects.isNull(fieldIndex)) {
                    if (!first) wrapper.or();
                    wrapper.eq(WarehouseAreasItem::getFieldsIndex, fieldIndex);
                    first = false;
                }
                if (!Cools.isEmpty(matnrCode)) {
                    if (!first) wrapper.or();
                    wrapper.eq(WarehouseAreasItem::getMatnrCode, matnrCode);
                    first = false;
                }
                if (!Cools.isEmpty(asnCode)) {
                    if (!first) wrapper.or();
                    wrapper.eq(WarehouseAreasItem::getAsnCode, asnCode);
                }
            });
        }
        // 打印查询参数到控制台
        logger.info("=== 查询可组托物料 ===");
        logger.info("查询参数 - code: {}, matnrCode: {}, asnCode: {}, batch: {}, fieldIndex: {}",
                code, matnrCode, asnCode, batch, fieldIndex);
        List<WarehouseAreasItem> list = warehouseAreasItemService.list(queryWrapper);
        logger.info("查询到 {} 条收货区库存数据", list.size());
        // 过滤条件:只返回可组托数量 > 0 的数据
        // 可组托数量 = anfme - workQty - qty
        if (!list.isEmpty()) {
            int beforeFilterSize = list.size();
            list.removeIf(e -> {
                // 如果anfme为null,移除该数据
                Double anfme = e.getAnfme();
                if (Objects.isNull(anfme) || anfme <= 0) {
                    logger.debug("过滤数据 - ID: {}, matnrCode: {}, anfme为null或<=0: {}",
                            e.getId(), e.getMatnrCode(), anfme);
                    return true; // 移除数量为null或<=0的数据
                }
                Double workQty = Objects.isNull(e.getWorkQty()) ? 0.0 : e.getWorkQty();
                Double qty = Objects.isNull(e.getQty()) ? 0.0 : e.getQty();
                // 可组托数量 = 总数量 - 已执行数量 - 已收货数量
                Double availableQty = anfme - workQty - qty;
                if (availableQty <= 0) {
                    logger.debug("过滤数据 - ID: {}, matnrCode: {}, anfme: {}, workQty: {}, qty: {}, 可组托数量: {} <= 0",
                            e.getId(), e.getMatnrCode(), anfme, workQty, qty, availableQty);
                }
                return availableQty <= 0; // 移除可组托数量 <= 0 的数据
            });
            // 如果过滤后数据为空,说明所有数据都被过滤掉了
            if (list.isEmpty() && beforeFilterSize > 0) {
                logger.warn("未找到可组托的物料,请检查:1.物料编码是否正确 2.物料是否已收货至收货区 3.是否还有可组托数量");
                logger.warn("查询条件 - code: {}, matnrCode: {}, asnCode: {}, batch: {}, fieldIndex: {}",
                        code, matnrCode, asnCode, batch, fieldIndex);
                logger.warn("查询到 {} 条数据,但全部被过滤(可组托数量 <= 0)", beforeFilterSize);
            }
        } else {
            logger.warn("未找到可组托的物料,请检查:1.物料编码是否正确 2.物料是否已收货至收货区 3.是否还有可组托数量");
            logger.warn("查询条件 - code: {}, matnrCode: {}, asnCode: {}, batch: {}, fieldIndex: {}",
                    code, matnrCode, asnCode, batch, fieldIndex);
            logger.warn("数据库查询结果为空,可能原因:1.物料编码不匹配 2.物料未收货至收货区 3.数据已被删除");
        }
        list.forEach(item -> {
            Map<String, String> fields = FieldsUtils.getFields(item.getFieldsIndex());
            item.setExtendFields(fields);
        });
        logger.info("返回 {} 条可组托物料数据", list.size());
        return R.ok(list);
    }
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java
@@ -463,12 +463,18 @@
    @Override
    public R receiveExMsg(ExMsgParams params) {
        if (Objects.isNull(params)) {
            log.error("RCS回调为空!");
            return R.error("参数不能为空!!");
        }
        log.info("========== 接收RCS回调 ==========");
        log.info("任务编号:{},事件类型:{}", params.getSeqNum(), params.getEventType());
        Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getTaskCode, params.getSeqNum()));
        if (Objects.isNull(task)) {
            throw new CoolException("任务不存在可已结束!!");
            log.error("任务不存在或已结束!任务编号:{}", params.getSeqNum());
            throw new CoolException("任务不存在或已结束!!");
        }
        log.info("查询到任务 - 任务编码:{},任务类型:{},当前状态:{}",
                task.getTaskCode(), task.getTaskType(), task.getTaskStatus());
        /**料箱搬运中, 修改站点状态*/
//        if (params.getEventType().equals(CallBackEvent.CALL_BACK_EVENT_OBIT.event)) {
@@ -493,21 +499,40 @@
                if (!task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type)) {
                    BasStation station = basStationService.getOne(new LambdaQueryWrapper<BasStation>().eq(BasStation::getStationName, task.getOrgSite()));
                    if (Objects.isNull(station)) {
                        log.error("入库站点不存在 - 站点名称:{},任务编码:{}", task.getOrgSite(), task.getTaskCode());
                        throw new CoolException("数据错误,站点不存在!!");
                    }
                    log.info("查询到入库站点 - 站点名称:{},站点类型:{},当前状态:{}",
                            station.getStationName(), station.getType(), station.getUseStatus());
                    if (station.getType().equals(StationTypeEnum.STATION_TYPE_NORMAL.type)) {
                        log.info("更新入库站点状态 - 站点名称:{},新状态:{}", station.getStationName(), LocStsType.LOC_STS_TYPE_O.type);
                        station.setUseStatus(LocStsType.LOC_STS_TYPE_O.type);
                        if (!basStationService.updateById(station)) {
                            log.error("入库站点状态修改失败 - 站点名称:{}", station.getStationName());
                            throw new CoolException("站点状态修改失败!!");
                        }
                        log.info("入库站点状态更新成功 - 站点名称:{}", station.getStationName());
                    }
                }
                if (!taskService.update(new LambdaUpdateWrapper<Task>()
                log.info("准备更新入库任务状态 - 任务编码:{},当前状态:{},目标状态:{}",
                        task.getTaskCode(), task.getTaskStatus(), TaskStsType.COMPLETE_IN.id);
                // 如果任务状态已经大于等于目标状态,跳过更新
                if (task.getTaskStatus() >= TaskStsType.COMPLETE_IN.id) {
                    log.warn("入库任务状态已大于等于目标状态,跳过更新 - 任务编码:{},当前状态:{},目标状态:{}",
                            task.getTaskCode(), task.getTaskStatus(), TaskStsType.COMPLETE_IN.id);
                } else {
                    boolean updated = taskService.update(new LambdaUpdateWrapper<Task>()
                        .lt(Task::getTaskStatus, TaskStsType.COMPLETE_IN.id)
                        .eq(Task::getTaskCode, task.getTaskCode())
                        .set(Task::getTaskStatus, TaskStsType.COMPLETE_IN.id))) {
                    throw new CoolException("任务状态修改失败!!");
                            .set(Task::getTaskStatus, TaskStsType.COMPLETE_IN.id));
                    if (!updated) {
                        log.error("入库任务状态修改失败 - 任务编码:{},当前状态:{},目标状态:{},可能任务状态已大于等于目标状态",
                                task.getTaskCode(), task.getTaskStatus(), TaskStsType.COMPLETE_IN.id);
                        throw new CoolException("任务状态修改失败!!当前任务状态:" + task.getTaskStatus() + ",目标状态:" + TaskStsType.COMPLETE_IN.id);
                    }
                    log.info("入库任务状态更新成功 - 任务编码:{}", task.getTaskCode());
                }
            } else if (task.getTaskType().equals(TaskType.TASK_TYPE_OUT.type)
                    || task.getTaskType().equals(TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)
@@ -519,22 +544,45 @@
                BasStation station = basStationService.getOne(new LambdaQueryWrapper<BasStation>()
                        .eq(BasStation::getStationName, task.getTargSite()));
                if (Objects.isNull(station)) {
                    log.error("出库站点不存在 - 站点名称:{},任务编码:{}", task.getTargSite(), task.getTaskCode());
                    throw new CoolException("数据错误,站点不存在!!");
                }
                log.info("查询到出库站点 - 站点名称:{},站点类型:{},当前状态:{}",
                        station.getStationName(), station.getType(), station.getUseStatus());
                if (station.getType().equals(StationTypeEnum.STATION_TYPE_NORMAL.type)) {
                    log.info("更新出库站点状态 - 站点名称:{},新状态:{}", station.getStationName(), LocStsType.LOC_STS_TYPE_F.type);
                    station.setUseStatus(LocStsType.LOC_STS_TYPE_F.type);
                    if (!basStationService.updateById(station)) {
                        log.error("出库站点状态修改失败 - 站点名称:{}", station.getStationName());
                        throw new CoolException("站点状态修改失败!!");
                    }
                    log.info("出库站点状态更新成功 - 站点名称:{}", station.getStationName());
                }
                if (!taskService.update(new LambdaUpdateWrapper<Task>().eq(Task::getTaskCode, task.getTaskCode())
                log.info("准备更新出库任务状态 - 任务编码:{},当前状态:{},目标状态:{}",
                        task.getTaskCode(), task.getTaskStatus(), TaskStsType.COMPLETE_OUT.id);
                // 如果任务状态已经大于等于目标状态,跳过更新
                if (task.getTaskStatus() >= TaskStsType.COMPLETE_OUT.id) {
                    log.warn("出库任务状态已大于等于目标状态,跳过更新 - 任务编码:{},当前状态:{},目标状态:{}",
                            task.getTaskCode(), task.getTaskStatus(), TaskStsType.COMPLETE_OUT.id);
                } else {
                    boolean updated = taskService.update(new LambdaUpdateWrapper<Task>().eq(Task::getTaskCode, task.getTaskCode())
                        .lt(Task::getTaskStatus, TaskStsType.COMPLETE_OUT.id)
                        .set(Task::getTaskStatus, TaskStsType.COMPLETE_OUT.id))) {
                    throw new CoolException("任务状态修改失败!!");
                            .set(Task::getTaskStatus, TaskStsType.COMPLETE_OUT.id));
                    if (!updated) {
                        log.error("出库任务状态修改失败 - 任务编码:{},当前状态:{},目标状态:{},可能任务状态已大于等于目标状态",
                                task.getTaskCode(), task.getTaskStatus(), TaskStsType.COMPLETE_OUT.id);
                        throw new CoolException("任务状态修改失败!!当前任务状态:" + task.getTaskStatus() + ",目标状态:" + TaskStsType.COMPLETE_OUT.id);
                    }
                    log.info("出库任务状态更新成功 - 任务编码:{}", task.getTaskCode());
                }
            }
        } else {
            log.warn("未处理的事件类型 - 事件类型:{},任务编码:{},任务类型:{}",
                    params.getEventType(), task.getTaskCode(), task.getTaskType());
        }
        log.info(JSONObject.toJSONString(params));
        log.info("========== RCS回调处理完成 ==========");
        log.info("处理结果:{}", JSONObject.toJSONString(params));
        return R.ok(JSONObject.toJSONString(params));
    }
@@ -547,7 +595,7 @@
    @Override
    public R pubWcsTask(WcsTaskParams params) {
        String rcsUrl = rcsApi.getHost() + ":" + rcsApi.getPort() + RcsConstant.pubTask;
        log.info("任务下发,请求地址: {}, 请求参数: {}", rcsUrl, JSONObject.toJSONString(params));
        log.info("任务下发,请求地址3: {}, 请求参数: {}", rcsUrl, JSONObject.toJSONString(params));
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        headers.add("api-version", "v2.0");
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java
@@ -8,7 +8,6 @@
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.framework.common.DateUtils;
import com.vincent.rsf.framework.common.R;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.api.config.RemotesInfoProperties;
@@ -252,6 +251,7 @@
    @Scheduled(cron = "0/55 * * * * ?  ")
    @Transactional(rollbackFor = Exception.class)
    public void pubTaskToWcs() {
        log.info("定时任务开始执行:任务下发到RCS");
        Long loginUserId = SystemAuthUtils.getLoginUserId();
        List<Integer> list = Arrays.asList(TaskType.TASK_TYPE_IN.type, TaskType.TASK_TYPE_OUT.type, TaskType.TASK_TYPE_LOC_MOVE.type, TaskType.TASK_TYPE_EMPITY_IN.type
                , TaskType.TASK_TYPE_CHECK_IN.type, TaskType.TASK_TYPE_MERGE_IN.type, TaskType.TASK_TYPE_EMPITY_OUT.type, TaskType.TASK_TYPE_PICK_IN.type,
@@ -261,12 +261,15 @@
                .in(Task::getTaskType, list)
                .in(Task::getTaskStatus, integers)
                .orderByDesc(Task::getSort));
        log.info("查询到待下发任务数量:{}", tasks.size());
        if (tasks.isEmpty()) {
            log.debug("没有待下发的任务,定时任务结束");
            return;
        }
        Config config = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_RUN_CHECK_ORDERS));
        if (!Objects.isNull(config) && !Objects.isNull(config.getVal())) {
            if (Boolean.parseBoolean(config.getVal())) {
                log.info("配置项AUTO_RUN_CHECK_ORDERS为true,跳过任务下发");
               return;
            }
        }
@@ -282,7 +285,9 @@
//            }
//        }
        /**下发普通站点任务,报错回滚,不再往下执行*/
        log.info("开始下发{}个任务到RCS", tasks.size());
        taskService.pubTaskToWcs(tasks);
        log.info("定时任务执行完成:任务下发到RCS");
    }
    /**
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
@@ -19,6 +19,7 @@
import com.vincent.rsf.server.manager.utils.OptimalAlgorithmUtil;
import com.vincent.rsf.server.system.constant.SerialRuleCode;
import com.vincent.rsf.server.system.utils.SerialRuleUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,6 +41,7 @@
 * @return
 * @time 2025/3/7 08:02
 */
@Slf4j
@Service("outStockServiceImpl")
public class OutStockServiceImpl extends ServiceImpl<AsnOrderMapper, WkOrder> implements OutStockService {
@@ -489,20 +491,82 @@
                List<LocItem> locItems = new ArrayList<>();
                LocItem locItem = locItemService.getById(param.getId());
                if (Objects.isNull(locItem)) {
                    throw new CoolException("库位明细不存在,ID:" + param.getId());
                }
                // 修复:构建查询条件,先构建基础条件,再根据值是否为空动态添加
                // 优先使用供应商批次匹配,如果没有则使用库存批次匹配
                LambdaQueryWrapper<WkOrderItem> orderItemWrapper = new LambdaQueryWrapper<WkOrderItem>()
                        .eq(WkOrderItem::getOrderId, outId)
                        .eq(WkOrderItem::getMatnrId, locItem.getMatnrId());
                if (StringUtils.isNotBlank(locItem.getBatch())) {
                    orderItemWrapper.eq(WkOrderItem::getSplrBatch, locItem.getBatch());
                // 优先使用供应商批次匹配
                if (StringUtils.isNotBlank(locItem.getSplrBatch())) {
                    orderItemWrapper.eq(WkOrderItem::getSplrBatch, locItem.getSplrBatch());
                } else if (StringUtils.isNotBlank(locItem.getBatch())) {
                    // 如果LocItem只有batch字段,尝试同时匹配WkOrderItem的batch和splrBatch字段
                    // 因为某些情况下LocItem的batch可能对应WkOrderItem的splrBatch
                    orderItemWrapper.and(wrapper -> wrapper
                            .eq(WkOrderItem::getBatch, locItem.getBatch())
                            .or()
                            .eq(WkOrderItem::getSplrBatch, locItem.getBatch())
                    );
                }
                if (StringUtils.isNotBlank(locItem.getFieldsIndex())) {
                    orderItemWrapper.eq(WkOrderItem::getFieldsIndex, locItem.getFieldsIndex());
                }
                WkOrderItem orderItem = outStockItemService.getOne(orderItemWrapper);
                // 如果找不到单据明细,且LocItem来自库存调整,则自动创建WkOrderItem
                if (Objects.isNull(orderItem)) {
                    throw new CoolException("单据明细不存在!!");
                    // 检查是否是库存调整产生的库存
                    if (locItem.getWkType() != null &&
                        locItem.getWkType().equals(Short.parseShort(OrderWorkType.ORDER_WORK_TYPE_STOCK_REVISE.type))) {
                        // 获取出库单信息
                        WkOrder outOrder = outStockService.getById(outId);
                        if (Objects.isNull(outOrder)) {
                            throw new CoolException("出库单据不存在!!");
                        }
                        log.info("库存调整产生的库存,自动创建WkOrderItem - 出库单ID:{},物料ID:{},批次:{}",
                                outId, locItem.getMatnrId(), locItem.getBatch());
                        // 创建WkOrderItem
                        orderItem = new WkOrderItem();
                        orderItem.setOrderId(outId)
                                .setOrderCode(outOrder.getCode())
                                .setMatnrId(locItem.getMatnrId())
                                .setMatnrCode(locItem.getMatnrCode())
                                .setMaktx(locItem.getMaktx())
                                .setBatch(StringUtils.isNotBlank(param.getBatch()) ? param.getBatch() : locItem.getBatch())
                                .setSplrBatch(StringUtils.isNotBlank(locItem.getSplrBatch()) ? locItem.getSplrBatch() :
                                             (StringUtils.isNotBlank(locItem.getBatch()) ? locItem.getBatch() : null))
                                .setFieldsIndex(locItem.getFieldsIndex())
                                .setAnfme(param.getOutQty())
                                .setWorkQty(0.0)
                                .setStockUnit(locItem.getUnit())
                                .setPurUnit(locItem.getUnit())
                                .setSpec(locItem.getSpec())
                                .setModel(locItem.getModel())
                                .setCreateBy(loginUserId)
                                .setUpdateBy(loginUserId)
                                .setCreateTime(new Date())
                                .setUpdateTime(new Date());
                        if (!outStockItemService.save(orderItem)) {
                            throw new CoolException("库存调整单据明细创建失败!!");
                        }
                        log.info("WkOrderItem创建成功 - ID:{},出库单ID:{},物料ID:{}",
                                orderItem.getId(), outId, locItem.getMatnrId());
                    } else {
                        throw new CoolException("单据明细不存在!!出库单ID:" + outId + ",物料ID:" + locItem.getMatnrId() +
                                (StringUtils.isNotBlank(locItem.getSplrBatch()) ? ",供应商批次:" + locItem.getSplrBatch() :
                                 StringUtils.isNotBlank(locItem.getBatch()) ? ",库存批次:" + locItem.getBatch() : "") +
                                (StringUtils.isNotBlank(locItem.getFieldsIndex()) ? ",字段索引:" + locItem.getFieldsIndex() : ""));
                    }
                }
                locItem.setOutQty(param.getOutQty())
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
@@ -462,6 +462,9 @@
                if (task.getTaskType().equals(TaskType.TASK_TYPE_IN.type)) {
                    //1.入库
                    complateInstock(task, loginUserId);
                } else if (task.getTaskType().equals(TaskType.TASK_TYPE_EMPITY_IN.type)) {
                    //10.空托入库
                    complateEmpityInStock(task, loginUserId);
                } else if (task.getTaskType().equals(TaskType.TASK_TYPE_PICK_IN.type) || task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_IN.type)) {
                    //53.拣料再入库
                    //57.盘点再入库
@@ -777,16 +780,132 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R removeTask(Long[] ids, Long loginUserId) {
        List<Integer> longs = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        // 先查询所有任务(不限制状态),用于检查RCS任务
        List<Integer> list = Arrays.asList(TaskType.TASK_TYPE_IN.type, TaskType.TASK_TYPE_OUT.type, TaskType.TASK_TYPE_PICK_AGAIN_OUT.type,
                TaskType.TASK_TYPE_CHECK_OUT.type, TaskType.TASK_TYPE_EMPITY_IN.type, TaskType.TASK_TYPE_LOC_MOVE.type,
                TaskType.TASK_TYPE_EMPITY_OUT.type, TaskType.TASK_TYPE_MERGE_OUT.type);
        List<Task> allTasks = this.list(new LambdaQueryWrapper<Task>()
                .in(Task::getTaskType, list)
                .in(Task::getId, (Object[]) ids));
        if (allTasks.isEmpty()) {
            throw new CoolException("任务不存在!!");
        }
        // 收集需要取消的RCS任务编号和批次编号(不限制状态,只要已下发到RCS就需要取消)
        List<String> rcsTaskCodes = new ArrayList<>();
        String batchNo = null;
        for (Task task : allTasks) {
            // 判断任务是否已下发到RCS
            // 入库任务:状态 >= WCS_EXECUTE_IN(2) 表示已下发
            // 出库任务:状态 >= WCS_EXECUTE_OUT(102) 表示已下发
            boolean isRcsTask = false;
            if (task.getTaskType() < 100) {
                // 入库任务
                if (task.getTaskStatus() >= TaskStsType.WCS_EXECUTE_IN.id) {
                    isRcsTask = true;
                }
            } else {
                // 出库任务
                if (task.getTaskStatus() >= TaskStsType.WCS_EXECUTE_OUT.id) {
                    isRcsTask = true;
                }
            }
            if (isRcsTask && StringUtils.isNotBlank(task.getTaskCode())) {
                rcsTaskCodes.add(task.getTaskCode());
                // 获取批次编号,优先使用任务的barcode,如果没有则使用任务编号
                if (StringUtils.isBlank(batchNo)) {
                    if (StringUtils.isNotBlank(task.getBarcode())) {
                        batchNo = task.getBarcode();
                    } else if (StringUtils.isNotBlank(task.getTaskCode())) {
                        // 如果任务没有barcode,使用任务编号作为批次编号
                        batchNo = task.getTaskCode();
                    }
                }
                log.info("任务已下发到RCS,需要取消RCS任务 - 任务ID:{},任务编号:{},任务状态:{},托盘码:{}",
                        task.getId(), task.getTaskCode(), task.getTaskStatus(), task.getBarcode());
            }
        }
        // 如果有任务已下发到RCS,先调用RCS取消接口
        boolean rcsCancelSuccess = false;
        if (!rcsTaskCodes.isEmpty()) {
            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()) {
                    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);
                    rcsCancelSuccess = true;
                } else {
                    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());
            }
        }
        // 查询符合取消条件的任务(状态为1、101、199)
        List<Integer> allowedStatuses = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id, TaskStsType.WAVE_SEED.id);
        List<Task> tasks = this.list(new LambdaQueryWrapper<Task>()
                .in(Task::getTaskType, list)
                .in(Task::getId, ids)
                .in(Task::getTaskStatus, longs));
        if (tasks.isEmpty()) {
                .in(Task::getId, (Object[]) ids)
                .in(Task::getTaskStatus, allowedStatuses));
        // 如果符合取消条件的任务为空,但RCS取消成功,允许继续(可能是任务状态已变更)
        if (tasks.isEmpty() && !rcsCancelSuccess) {
            throw new CoolException("任务已处执行状态不可取消!!");
        }
        // 如果符合取消条件的任务为空,但RCS取消成功,记录日志并返回
        if (tasks.isEmpty() && rcsCancelSuccess) {
            log.warn("WMS内部任务状态不符合取消条件,但RCS任务已成功取消 - 任务ID:{}", Arrays.toString(ids));
            return R.ok("RCS任务已取消,WMS内部任务状态已变更");
        }
        for (Task task : tasks) {
            //取消移库任务
@@ -1402,13 +1521,28 @@
    public void pubTaskToWcs(List<Task> tasks) {
        WcsTaskParams taskParams = new WcsTaskParams();
        List<TaskItemParam> items = new ArrayList<>();
        // 设置批次编号(使用第一个任务的barcode,如果为null则使用任务编码)
        String batchNo = null;
        if (!tasks.isEmpty()) {
            Task firstTask = tasks.get(0);
            batchNo = StringUtils.isNotBlank(firstTask.getBarcode())
                    ? firstTask.getBarcode()
                    : firstTask.getTaskCode();
        }
        if (StringUtils.isBlank(batchNo)) {
            // 如果批次编号仍为空,生成一个默认值
            batchNo = "BATCH_" + System.currentTimeMillis();
        }
        taskParams.setBatchNo(batchNo);
        log.info("设置批次编号:{}", batchNo);
        tasks.forEach(task -> {
            TaskItemParam itemParam = new TaskItemParam();
            //任务类型,任务编码
            itemParam.setTaskType(RcsTaskType.getTypeDesc(task.getTaskType()))
                    .setSeqNum(task.getTaskCode());
            //主参数
            taskParams.setBatch(task.getBarcode());
            //任务编码(对应seqNum)
            itemParam.setTaskNo(task.getTaskCode());
            itemParam.setPriority(1);
            BasStation station = null;
            if (!task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type)) {
                station = basStationService.getOne(new LambdaQueryWrapper<BasStation>().eq(BasStation::getStationName, task.getTargSite()));
@@ -1474,27 +1608,50 @@
            }
            items.add(itemParam);
        });
        taskParams.setTaskList(items);
        taskParams.setTasks(items);
        /**任务下发接口*/
        String pubTakUrl = rcsApi.getHost() + ":" + rcsApi.getPort() + RcsConstant.pubTask;
        /**RCS基础配置链接*/
        log.info("任务下发,请求地址: {}, 请求参数: {}", pubTakUrl, JSONObject.toJSONString(taskParams));
        log.info("========== 开始下发任务到RCS ==========");
        log.info("RCS请求地址:{}", pubTakUrl);
        log.info("批次编号:{}", batchNo);
        log.info("任务数量:{}", tasks.size());
        log.info("任务列表详情:");
        tasks.forEach(task -> {
            log.info("  - 任务编码:{},任务类型:{},源库位:{},目标库位:{},源站点:{},目标站点:{}",
                    task.getTaskCode(), task.getTaskType(), task.getOrgLoc(),
                    task.getTargLoc(), task.getOrgSite(), task.getTargSite());
        });
        log.info("请求参数:{}", JSONObject.toJSONString(taskParams));
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        headers.add("api-version", "v2.0");
        HttpEntity httpEntity = new HttpEntity(taskParams, headers);
        long startTime = System.currentTimeMillis();
        ResponseEntity<String> exchange = restTemplate.exchange(pubTakUrl, HttpMethod.POST, httpEntity, String.class);
        log.info("任务下发后,响应结果: {}", exchange);
        long endTime = System.currentTimeMillis();
        log.info("RCS响应耗时:{}ms", (endTime - startTime));
        log.info("RCS响应状态码:{}", exchange.getStatusCode());
        log.info("RCS响应头:{}", exchange.getHeaders());
        log.info("RCS响应体:{}", exchange.getBody());
        if (Objects.isNull(exchange.getBody())) {
            throw new CoolException("任务下发失败!!");
            log.error("========== RCS任务下发失败 ==========");
            log.error("RCS响应体为空,无法解析响应结果");
            log.error("请求地址:{}", pubTakUrl);
            log.error("请求参数:{}", JSONObject.toJSONString(taskParams));
            throw new CoolException("任务下发失败,RCS响应体为空!!");
        } else {
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                objectMapper.coercionConfigDefaults()
                        .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty);
                CommonResponse result = objectMapper.readValue(exchange.getBody(), CommonResponse.class);
                log.info("RCS响应解析结果 - code:{},msg:{},data:{}",
                        result.getCode(), result.getMsg(), result.getData());
                if (result.getCode() == 200) {
                    log.info("RCS任务下发成功,开始更新任务状态");
                    tasks.forEach(task -> {
                        log.info("更新任务状态 - 任务编码:{},任务类型:{}", task.getTaskCode(), task.getTaskType());
                        if (task.getTaskType().equals(TaskType.TASK_TYPE_IN.type)
                                || task.getTaskType().equals(TaskType.TASK_TYPE_PICK_IN.type)
                                || task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_IN.type)
@@ -1507,10 +1664,12 @@
                                throw new CoolException("站点不存在!!");
                            }
                            log.info("更新入库任务状态 - 任务编码:{},新状态:{}", task.getTaskCode(), TaskStsType.WCS_EXECUTE_IN.id);
                            if (!taskService.update(new LambdaUpdateWrapper<Task>().eq(Task::getTaskCode, task.getTaskCode())
                                    .set(Task::getTaskStatus, TaskStsType.WCS_EXECUTE_IN.id))) {
                                throw new CoolException("任务状态修改失败!!");
                            }
                            log.info("入库任务状态更新成功 - 任务编码:{}", task.getTaskCode());
                            /**排除移库功能*/
                            if (!task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type)) {
                                /**如果是普通站点,修改站点状态为出库预约*/
@@ -1531,10 +1690,12 @@
                                throw new CoolException("站点不存在!!");
                            }
                            log.info("更新出库任务状态 - 任务编码:{},新状态:{}", task.getTaskCode(), TaskStsType.WCS_EXECUTE_OUT.id);
                            if (!taskService.update(new LambdaUpdateWrapper<Task>().eq(Task::getTaskCode, task.getTaskCode())
                                    .set(Task::getTaskStatus, TaskStsType.WCS_EXECUTE_OUT.id))) {
                                throw new CoolException("任务状态修改失败!!");
                            }
                            log.info("出库任务状态更新成功 - 任务编码:{}", task.getTaskCode());
                            /**如果是普通站点,修改站点状态为入库预约*/
                            if (curSta.getType().equals(StationTypeEnum.STATION_TYPE_NORMAL.type)) {
                                curSta.setUseStatus(LocStsType.LOC_STS_TYPE_S.type);
@@ -1544,15 +1705,27 @@
                            }
                        }
                    });
                    log.info("========== RCS任务下发完成,共{}个任务状态已更新 ==========", tasks.size());
                } else {
                    log.error(JSONObject.toJSONString(result));
                    log.error("========== RCS任务下发失败 ==========");
                    log.error("RCS返回错误 - code:{},msg:{},data:{}",
                            result.getCode(), result.getMsg(), result.getData());
                    log.error("失败的任务列表:");
                    tasks.forEach(task -> {
                        log.error("  - 任务编码:{},任务类型:{}", task.getTaskCode(), task.getTaskType());
                    });
//                    throw new CoolException("任务下发失败!!");
                }
            } catch (JsonProcessingException e) {
                throw new CoolException(e.getMessage());
                log.error("========== RCS任务下发异常 ==========");
                log.error("解析RCS响应失败,响应体:{}", exchange.getBody(), e);
                throw new CoolException("解析RCS响应失败:" + e.getMessage());
            } catch (Exception e) {
                log.error("========== RCS任务下发异常 ==========");
                log.error("任务下发过程中发生异常", e);
                throw e;
            }
        }
    }/**
@@ -1636,6 +1809,80 @@
    }
    /**
     * 空托入库完成
     *
     * @param task
     * @param loginUserId
     */
    @Transactional(rollbackFor = Exception.class)
    public synchronized void complateEmpityInStock(Task task, Long loginUserId) {
        if (Objects.isNull(task)) {
            return;
        }
        Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getCode, task.getTargLoc()));
        if (Objects.isNull(loc)) {
            throw new CoolException("目标库位不存在!");
        }
        if (!loc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_S.type)) {
            throw new CoolException("当前库位状态不处于S.入库预约,不可执行入库操作!");
        }
        List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, task.getId()));
        if (taskItems.isEmpty()) {
            throw new CoolException("任务明细不存在!!");
        }
        //更新库位明细
        saveLocItem(taskItems, task.getId(), loginUserId);
        //空托入库不校验组托信息,直接处理任务明细
        //如果有任务明细关联了组托,也需要保存入出库明细和移出收货区库存
        Map<Long, List<TaskItem>> orderMap = taskItems.stream().collect(Collectors.groupingBy(TaskItem::getSource));
        orderMap.keySet().forEach(key -> {
            // 如果source不为空,说明有关联的组托明细,需要处理
            if (key != null && key > 0) {
                WaitPakinItem pakinItem = waitPakinItemService.getById(key);
                if (!Objects.isNull(pakinItem)) {
                    List<TaskItem> items = orderMap.get(key);
                    //保存入出库明细
                    saveStockItems(items, task, pakinItem.getId(), pakinItem.getAsnCode(), pakinItem.getWkType(), pakinItem.getType(), loginUserId);
                    //移出收货区库存, 修改组托状态
                    removeReceiptStock(pakinItem, loginUserId);
                }
            }
        });
        // 处理组托状态(如果有组托关联)
        Set<Long> pkinItemIds = taskItems.stream()
                .map(TaskItem::getSource)
                .filter(source -> source != null && source > 0)
                .collect(Collectors.toSet());
        if (!pkinItemIds.isEmpty()) {
            List<WaitPakinItem> pakinItems = waitPakinItemService.list(new LambdaQueryWrapper<WaitPakinItem>().in(WaitPakinItem::getId, pkinItemIds));
            if (!pakinItems.isEmpty()) {
                Set<Long> pakinIds = pakinItems.stream().map(WaitPakinItem::getPakinId).collect(Collectors.toSet());
                if (!waitPakinService.update(new LambdaUpdateWrapper<WaitPakin>()
                        .set(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_TASK_DONE.val)
                        .set(WaitPakin::getUpdateBy, loginUserId)
                        .in(WaitPakin::getId, pakinIds))) {
                    throw new CoolException("组拖状态修改失败!!");
                }
            }
        }
        /**修改库位状态为F.在库*/
        if (!locService.update(new LambdaUpdateWrapper<Loc>().set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_F.type).eq(Loc::getCode, task.getTargLoc()))) {
            throw new CoolException("库位状态修改失败!!");
        }
        if (!this.update(new LambdaUpdateWrapper<Task>().eq(Task::getId, task.getId()).set(Task::getTaskStatus, TaskStsType.UPDATED_IN.id))) {
            throw new CoolException("任务状态修改失败!!");
        }
    }
    /**
     * @param
     * @param loginUserId
     * @return
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaitPakinServiceImpl.java
@@ -75,8 +75,11 @@
                    if (Objects.isNull(warehouseAreasItems)) {
                        throw new CoolException("物料未送至收货区或已组托完成移出收货区!!");
                    }
                    if (item.getFieldsIndex().equals(warehouseAreasItems.getFieldsIndex())) {
                        throw new CoolException("票号:" + item.getExtendFields().get("crushNo") + "已组托,不可重复组托!!");
                    // 只在票号不为空时才进行重复检查
                    if (!Objects.isNull(item.getFieldsIndex()) && !Objects.isNull(warehouseAreasItems.getFieldsIndex())
                        && item.getFieldsIndex().equals(warehouseAreasItems.getFieldsIndex())) {
                        String crushNo = item.getExtendFields() != null ? item.getExtendFields().get("crushNo") : "";
                        throw new CoolException("票号:" + crushNo + "已组托,不可重复组托!!");
                    }
                }
            });