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());