chen.lin
7 小时以前 f574055ed80a64cbccd601b200afc437a87b52eb
RCS入库流程优化
10个文件已修改
274 ■■■■ 已修改文件
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/dto/LocationAllocateResponse.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/WmsRcsServiceImpl.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/WcsController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InTaskMsgDto.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ExMsgParams.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Task.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/dto/LocationAllocateResponse.java
@@ -16,4 +16,10 @@
    @ApiModelProperty("库位号")
    private String locNo;
    @ApiModelProperty("批次号")
    private String batchNo;
    @ApiModelProperty("任务号")
    private String taskNo;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/WmsRcsServiceImpl.java
@@ -355,8 +355,10 @@
        
        // 将TaskReportParams转换为ExMsgParams格式(taskNo -> seqNum)
        // 根据RCS新接口规范,taskNo对应旧接口的seqNum
        // 批次号用于验证任务主表,任务号用于查询和关联明细
        JSONObject exMsgParams = new JSONObject();
        exMsgParams.put("seqNum", params.getTaskNo()); // taskNo映射到seqNum
        exMsgParams.put("batchNo", params.getBatchNo()); // 批次号用于精确关联
        // eventType设置为END,表示任务完成(根据业务需求可能需要调整)
        exMsgParams.put("eventType", "END");
        exMsgParams.put("robotCode", null);
@@ -455,11 +457,15 @@
                    JSONObject data = responseJson.getJSONObject("data");
                    if (data != null) {
                        String locNo = data.getString("locNo");
                        String batchNo = data.getString("batchNo");
                        String taskNo = data.getString("taskNo");
                        log.info("========== 申请入库任务成功 ==========");
                        log.info("分配库位号:{}", locNo);
                        log.info("分配库位号:{},批次号:{},任务号:{}", locNo, batchNo, taskNo);
                        
                        LocationAllocateResponse response = new LocationAllocateResponse();
                        response.setLocNo(locNo);
                        response.setBatchNo(batchNo);
                        response.setTaskNo(taskNo);
                        return R.ok(response);
                    } else {
                        log.error("========== 申请入库任务失败 ==========");
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/WcsController.java
@@ -126,7 +126,7 @@
    /**
     * @author Ryan
     * @date 2026/2/6
     * @description: 申请入库任务,分配库位(内部接口)
     * @description: 申请入库任务,分配库位
     * @version 1.0
     */
    @ApiOperation("申请入库任务,分配库位")
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/dto/InTaskMsgDto.java
@@ -13,4 +13,5 @@
    private String locNo;
    private String workNo;
    private Long taskId;
}
rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/ExMsgParams.java
@@ -12,6 +12,9 @@
    private String seqNum;
    @ApiModelProperty("批次号(用于精确关联任务明细)")
    private String batchNo;
    @ApiModelProperty("事件类型: {START: 下发成功,  OTBIN: 取货成功, END: 放货成功}")
    private String eventType;
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/MobileServiceImpl.java
@@ -623,57 +623,74 @@
        //TODO 后续需根据策略配置,获取组拖数据。如:混装,按批次混装等
        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) {
            // 单个条件,直接查询
        // 如果有ASN单号,则只查询该单号下的物料(ASN单号作为必须条件)
        if (!Cools.isEmpty(asnCode)) {
            // ASN单号作为必须条件
            queryWrapper.eq(WarehouseAreasItem::getAsnCode, asnCode);
            // 如果同时有物料编码,则查询该ASN单号下的该物料
            if (!Cools.isEmpty(matnrCode)) {
                queryWrapper.eq(WarehouseAreasItem::getMatnrCode, matnrCode);
            }
            // 如果同时有批次,则查询该ASN单号下的该批次
            if (!Cools.isEmpty(batch)) {
                queryWrapper.eq(WarehouseAreasItem::getSplrBatch, batch);
            }
            // 如果同时有票号,则查询该ASN单号下的该票号
            if (!Objects.isNull(fieldIndex)) {
                queryWrapper.eq(WarehouseAreasItem::getFieldsIndex, fieldIndex);
            }
            // 如果同时有跟踪码,则查询该ASN单号下的该跟踪码
            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;
            // 没有ASN单号时,可以扫描任意物料组托,使用OR连接多个条件
            // 统计有效条件数量
            int conditionCount = 0;
            if (!Cools.isEmpty(code)) conditionCount++;
            if (!Cools.isEmpty(batch)) conditionCount++;
            if (!Objects.isNull(fieldIndex)) conditionCount++;
            if (!Cools.isEmpty(matnrCode)) conditionCount++;
            // 如果只有一个条件,直接使用eq
            if (conditionCount == 1) {
                if (!Cools.isEmpty(code)) {
                    wrapper.eq(WarehouseAreasItem::getTrackCode, code);
                    first = false;
                    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);
                }
                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);
                }
            });
            } else if (conditionCount > 1) {
                // 多个条件,使用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);
                    }
                });
            }
        }
        
        // 打印查询参数到控制台
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java
@@ -150,6 +150,7 @@
        // 设置工作单号并返回
        locNo.setWorkNo(ruleCode);
        locNo.setTaskId(task.getId());
        return locNo;
    }
@@ -174,7 +175,7 @@
    private WaitPakin validateWaitPakin(String barcode) {
        WaitPakin waitPakin = waitPakinService.getOne(new LambdaQueryWrapper<WaitPakin>()
                .eq(WaitPakin::getBarcode, barcode)
                .eq(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_DONE.val));
                .in(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_DONE.val, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val));
        if (Cools.isEmpty(waitPakin)) {
            throw new CoolException("请检查组拖状态是否完成!!");
@@ -231,13 +232,30 @@
    /**
     * 获取并验证组拖明细
     * 只返回未完全使用的明细(workQty < anfme 或 workQty 为 null)
     */
    private List<WaitPakinItem> getWaitPakinItems(Long pakinId) {
        List<WaitPakinItem> waitPakinItems = waitPakinItemService.list(new LambdaQueryWrapper<WaitPakinItem>().eq(WaitPakinItem::getPakinId, pakinId));
        List<WaitPakinItem> waitPakinItems = waitPakinItemService.list(new LambdaQueryWrapper<WaitPakinItem>()
                .eq(WaitPakinItem::getPakinId, pakinId));
        if (waitPakinItems.isEmpty()) {
            throw new CoolException("数据错误:组拖明细不存在");
        }
        return waitPakinItems;
        // 过滤掉已完全使用的明细(workQty >= anfme)
        List<WaitPakinItem> availableItems = waitPakinItems.stream()
                .filter(item -> {
                    Double workQty = item.getWorkQty() == null ? 0.0 : item.getWorkQty();
                    Double anfme = item.getAnfme() == null ? 0.0 : item.getAnfme();
                    return workQty < anfme; // 只返回未完全使用的明细
                })
                .collect(Collectors.toList());
        if (availableItems.isEmpty()) {
            throw new CoolException("组拖明细已全部使用,无法再次创建任务");
        }
        return availableItems;
    }
    /**
@@ -261,6 +279,15 @@
        if (!taskItemService.saveBatch(taskItems)) {
            throw new CoolException("任务明细保存失败!!");
        }
        // 更新组托明细的执行数量,标记明细已被提取使用
        waitPakinItems.forEach(item -> {
            if (!waitPakinItemService.update(new LambdaUpdateWrapper<WaitPakinItem>()
                    .set(WaitPakinItem::getWorkQty, item.getAnfme())
                    .eq(WaitPakinItem::getId, item.getId()))) {
                throw new CoolException("组托明细执行数量修改失败!!");
            }
        });
    }
    /**
@@ -467,14 +494,88 @@
            return R.error("参数不能为空!!");
        }
        log.info("========== 接收RCS回调 ==========");
        log.info("任务编号:{},事件类型:{}", params.getSeqNum(), params.getEventType());
        Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getTaskCode, params.getSeqNum()));
        log.info("任务编号:{},批次号:{},事件类型:{}", params.getSeqNum(), params.getBatchNo(), params.getEventType());
        // 优先使用批次号查询任务主表(批次号 = 任务编码,更可靠)
        String taskCode = params.getBatchNo();
        String taskNo = params.getSeqNum();
        String locCode = null; // 库位代码(多库位任务时从任务号中提取)
        // 如果批次号为空,使用任务号查询
        if (StringUtils.isBlank(taskCode)) {
            taskCode = taskNo;
        }
        // 处理出库任务多库位的情况:任务号格式可能是 "任务编码_库位代码"
        // 如果任务号包含下划线,提取库位代码
        if (StringUtils.isNotBlank(taskNo) && taskNo.contains("_")) {
            locCode = taskNo.substring(taskNo.lastIndexOf("_") + 1);
            // 如果批次号为空,从任务号中提取原始任务编码
            if (StringUtils.isBlank(taskCode)) {
                taskCode = taskNo.substring(0, taskNo.lastIndexOf("_"));
            }
            log.info("检测到多库位任务,提取库位代码:{},原始任务编码:{}", locCode, taskCode);
        }
        // 使用批次号(任务编码)查询任务主表
        Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getTaskCode, taskCode));
        if (Objects.isNull(task)) {
            log.error("任务不存在或已结束!任务编号:{}", params.getSeqNum());
            throw new CoolException("任务不存在或已结束!!");
            log.error("任务不存在或已结束!任务编号:{},批次号:{}", taskNo, taskCode);
            throw new CoolException("任务不存在或已结束!!任务编号:" + taskNo + ",批次号:" + taskCode);
        }
        // 验证批次号和任务号的关联关系
        if (StringUtils.isNotBlank(params.getBatchNo()) && !params.getBatchNo().equals(task.getTaskCode())) {
            log.warn("批次号与任务编码不匹配!批次号:{},任务编码:{}", params.getBatchNo(), task.getTaskCode());
        }
        log.info("查询到任务 - 任务编码:{},任务类型:{},当前状态:{}", 
                task.getTaskCode(), task.getTaskType(), task.getTaskStatus());
        // 对于多库位出库任务,通过库位代码精确关联任务明细
        List<TaskItem> relatedTaskItems = null;
        if (StringUtils.isNotBlank(locCode)) {
            // 1. 通过库位代码查询LocItem
            LocItem locItem = locItemService.getOne(new LambdaQueryWrapper<LocItem>().eq(LocItem::getLocCode, locCode));
            if (Objects.isNull(locItem)) {
                log.warn("未找到库位明细!库位代码:{},任务编码:{}", locCode, task.getTaskCode());
            } else {
                // 2. 通过任务ID和LocItem.id(即TaskItem.source)查询对应的任务明细
                relatedTaskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>()
                        .eq(TaskItem::getTaskId, task.getId())
                        .eq(TaskItem::getSource, locItem.getId()));
                if (relatedTaskItems.isEmpty()) {
                    log.warn("未找到对应的任务明细!任务编码:{},库位代码:{},LocItem.id:{}",
                            task.getTaskCode(), locCode, locItem.getId());
                } else {
                    log.info("通过库位代码精确关联到任务明细 - 任务编码:{},库位代码:{},任务明细数量:{}",
                            task.getTaskCode(), locCode, relatedTaskItems.size());
                }
            }
        }
        // 验证任务状态:只有已下发到RCS的任务才能被回调处理
        // 入库任务:状态 >= WCS_EXECUTE_IN(2) 表示已下发
        // 出库任务:状态 >= WCS_EXECUTE_OUT(102) 表示已下发
        boolean isValidStatus = false;
        if (task.getTaskType() < 100) {
            // 入库任务
            if (task.getTaskStatus() >= TaskStsType.WCS_EXECUTE_IN.id) {
                isValidStatus = true;
            }
        } else {
            // 出库任务
            if (task.getTaskStatus() >= TaskStsType.WCS_EXECUTE_OUT.id) {
                isValidStatus = true;
            }
        }
        if (!isValidStatus) {
            log.error("任务状态不正确,无法处理回调!任务编码:{},任务类型:{},当前状态:{},期望状态:已下发到RCS",
                    task.getTaskCode(), task.getTaskType(), task.getTaskStatus());
            throw new CoolException("任务状态不正确,无法处理回调!任务编码:" + task.getTaskCode() +
                    ",当前状态:" + task.getTaskStatus() + ",任务可能未下发到RCS或已被处理");
        }
        /**料箱搬运中, 修改站点状态*/
//        if (params.getEventType().equals(CallBackEvent.CALL_BACK_EVENT_OBIT.event)) {
@@ -1003,13 +1104,16 @@
        // 8. 创建并保存任务明细
        // 9. 更新组托状态
        InTaskMsgDto msgDto = createInTask(param);
        log.info("========== 申请入库任务成功 ==========");
        log.info("任务编码:{},库位号:{}", msgDto.getWorkNo(), msgDto.getLocNo());
        //RCS已经在输送线上,所以不需要下发任务
        taskService.updateById(new Task(){{setId(msgDto.getTaskId());setTaskStatus(2);}});
        log.info("========== RCS-申请入库任务成功 ==========");
        log.info("RCS-返回 任务编码:{},库位号:{}", msgDto.getWorkNo(), msgDto.getLocNo());
        // 返回结果,只返回库位号(根据接口文档要求)
        JSONObject result = new JSONObject();
        result.put("locNo", msgDto.getLocNo());
        result.put("batchNo", msgDto.getWorkNo());
        result.put("taskNo", msgDto.getWorkNo());
        return R.ok(result);
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Task.java
@@ -74,7 +74,7 @@
    /**
     * 任务类型
     */
    @ApiModelProperty(value= "任务类型")
        @ApiModelProperty(value= "任务类型")
    private Integer taskType;
    /**
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java
@@ -253,9 +253,19 @@
    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,
                TaskType.TASK_TYPE_PICK_AGAIN_OUT.type, TaskType.TASK_TYPE_CHECK_OUT.type, TaskType.TASK_TYPE_MERGE_OUT.type);
        List<Integer> list = Arrays.asList(
                 TaskType.TASK_TYPE_LOC_MOVE.type
                ,TaskType.TASK_TYPE_OUT.type
                ,TaskType.TASK_TYPE_EMPITY_OUT.type
                ,TaskType.TASK_TYPE_CHECK_OUT.type
                ,TaskType.TASK_TYPE_MERGE_OUT.type
                ,TaskType.TASK_TYPE_PICK_AGAIN_OUT.type
                ,TaskType.TASK_TYPE_IN.type
                ,TaskType.TASK_TYPE_EMPITY_IN.type
                ,TaskType.TASK_TYPE_CHECK_IN.type
                ,TaskType.TASK_TYPE_MERGE_IN.type
                ,TaskType.TASK_TYPE_PICK_IN.type
        );
        List<Integer> integers = Arrays.asList(TaskStsType.GENERATE_IN.id, TaskStsType.GENERATE_OUT.id);
        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>()
                .in(Task::getTaskType, list)
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
@@ -1781,7 +1781,10 @@
                    // 为每个不同的库位创建一个TaskItemParam
                    for (String locCode : locCodes) {
                        TaskItemParam outItemParam = new TaskItemParam();
                        outItemParam.setTaskNo(task.getTaskCode());
                        String taskNo = locCodes.size() > 1
                                ? task.getTaskCode() + "_" + locCode
                                : task.getTaskCode();
                        outItemParam.setTaskNo(taskNo);
                        outItemParam.setPriority(1);
                        outItemParam.setOriLoc(locCode);
                        outItemParam.setDestSta(task.getTargSite());