| | |
| | | import com.vincent.rsf.server.api.utils.SlaveProperties; |
| | | import com.vincent.rsf.server.manager.entity.*; |
| | | import com.vincent.rsf.server.manager.service.*; |
| | | import com.vincent.rsf.server.manager.controller.params.PakinItem; |
| | | import com.vincent.rsf.server.manager.controller.params.WaitPakinParam; |
| | | import com.vincent.rsf.server.manager.service.impl.LocServiceImpl; |
| | | import com.vincent.rsf.server.system.constant.GlobalConfigCode; |
| | | import com.vincent.rsf.server.system.entity.Config; |
| | | import com.vincent.rsf.server.system.service.ConfigService; |
| | | import com.vincent.rsf.server.system.utils.SystemAuthUtils; |
| | | import com.vincent.rsf.server.system.constant.SerialRuleCode; |
| | | import com.vincent.rsf.server.manager.enums.LocStsType; |
| | | import com.vincent.rsf.server.system.utils.SerialRuleUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import lombok.val; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.BeanUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.web.client.RestTemplate; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | |
| | | private RestTemplate restTemplate; |
| | | @Autowired |
| | | private RemotesInfoProperties.RcsApi rcsApi; |
| | | @Autowired |
| | | private ConfigService configService; |
| | | @Autowired |
| | | private MatnrService matnrService; |
| | | @Autowired |
| | | private AsnOrderService asnOrderService; |
| | | @Autowired |
| | | private AsnOrderItemService asnOrderItemService; |
| | | @Autowired |
| | | private com.vincent.rsf.server.api.service.MobileService mobileService; |
| | | |
| | | |
| | | @Override |
| | |
| | | |
| | | // 2. 若未命中拣料/盘点入库,再校验组托并继续其他入库逻辑 |
| | | if (pickInTask == null && checkInTask == null) { |
| | | String barcode = param.getBarcode(); |
| | | // 该托盘已在库存中,不可重复申请入库 |
| | | List<Loc> inStock = locService.list(new LambdaQueryWrapper<Loc>().eq(Loc::getBarcode, barcode)); |
| | | if (!inStock.isEmpty()) { |
| | | throw new CoolException("barcode=" + barcode + ": 该托盘已在库,不可重复申请入库"); |
| | | } |
| | | // 该托盘出库中未完成,不可申请入库 |
| | | List<Integer> outboundTaskTypes = Arrays.asList( |
| | | TaskType.TASK_TYPE_OUT.type, |
| | | TaskType.TASK_TYPE_PICK_AGAIN_OUT.type, |
| | | TaskType.TASK_TYPE_MERGE_OUT.type, |
| | | TaskType.TASK_TYPE_CHECK_OUT.type, |
| | | TaskType.TASK_TYPE_EMPITY_OUT.type |
| | | ); |
| | | Task outboundTask = taskService.getOne(new LambdaQueryWrapper<Task>() |
| | | .eq(Task::getBarcode, barcode) |
| | | .in(Task::getTaskType, outboundTaskTypes) |
| | | .lt(Task::getTaskStatus, TaskStsType.COMPLETE_OUT.id)); |
| | | if (outboundTask != null) { |
| | | throw new CoolException("barcode=" + barcode + ": 该托盘出库中未完成,不可申请入库"); |
| | | } |
| | | |
| | | // 按 barcode 加锁,避免同一 barcode 并发请求重复自动组托、重复生成入库单 |
| | | String barcodeForLock = param.getBarcode(); |
| | | synchronized ((barcodeForLock != null ? barcodeForLock : "").intern()) { |
| | | waitPakin = waitPakinService.getOne(new LambdaQueryWrapper<WaitPakin>() |
| | | .eq(WaitPakin::getBarcode, param.getBarcode()) |
| | | .in(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_DONE.val, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val)); |
| | | // 空托盘无组托时:若配置启用则按 AUTO_FULL_OUT_MATNR_CODE 自动组托并生成入库单,再继续入库任务逻辑 |
| | | if (waitPakin == null) { |
| | | tryAutoPakinForBarcode(param.getBarcode()); |
| | | waitPakin = waitPakinService.getOne(new LambdaQueryWrapper<WaitPakin>() |
| | | .eq(WaitPakin::getBarcode, param.getBarcode()) |
| | | .in(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_DONE.val, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val)); |
| | | } |
| | | } |
| | | waitPakin = validateWaitPakin(param.getBarcode()); |
| | | waitPakinItems = waitPakinItemService.list( |
| | | new LambdaQueryWrapper<WaitPakinItem>().eq(WaitPakinItem::getPakinId, waitPakin.getId())); |
| | | } |
| | | // 检查其他入库任务类型(用箱号查询,状态为1或2) |
| | | // 注意:盘点入库已单独处理,不再包含在此列表中 |
| | | // 检查该托盘号是否已有入库任务(含进行中、已完成),有则直接复用返回,拦截重复提交 |
| | | List<Integer> otherInboundTaskTypes = Arrays.asList( |
| | | TaskType.TASK_TYPE_IN.type, |
| | | TaskType.TASK_TYPE_MERGE_IN.type, |
| | |
| | | Task existingInTask = taskService.getOne(new LambdaQueryWrapper<Task>() |
| | | .eq(Task::getBarcode, param.getBarcode()) |
| | | .in(Task::getTaskType, otherInboundTaskTypes) |
| | | .in(Task::getTaskStatus, TaskStsType.GENERATE_IN.id, TaskStsType.WCS_EXECUTE_IN.id) |
| | | .orderByDesc(Task::getCreateTime) |
| | | .last("LIMIT 1")); |
| | | |
| | | if (Objects.nonNull(existingInTask)) { |
| | | log.info("找到匹配的其他入库任务 - 任务编码:{},任务类型:{},箱号:{}", |
| | | existingInTask.getTaskCode(), existingInTask.getTaskType(), param.getBarcode()); |
| | | log.info("找到该托盘号已有入库任务,复用并拦截重复提交 - 任务编码:{},箱号:{},状态:{}", |
| | | existingInTask.getTaskCode(), param.getBarcode(), existingInTask.getTaskStatus()); |
| | | |
| | | // 检查组托明细是否有订单编码(任务编号) |
| | | List<WaitPakinItem> itemsWithAsnCode = waitPakinItems.stream() |
| | | .filter(item -> StringUtils.isNotBlank(item.getAsnCode())) |
| | | .collect(Collectors.toList()); |
| | | |
| | | if (!itemsWithAsnCode.isEmpty()) { |
| | | log.info("组托档有任务编号,使用现有入库任务单号 - 任务编码:{},箱号:{},任务编号数量:{}", |
| | | existingInTask.getTaskCode(), param.getBarcode(), itemsWithAsnCode.size()); |
| | | |
| | | // 更新入库站点信息(如果与当前申请的站点不同) |
| | | if (StringUtils.isNotBlank(param.getSourceStaNo()) && |
| | | !param.getSourceStaNo().equals(existingInTask.getOrgSite())) { |
| | | log.info("更新入库任务的入库站点 - 任务编码:{},原站点:{},新站点:{}", |
| | | existingInTask.getTaskCode(), existingInTask.getOrgSite(), param.getSourceStaNo()); |
| | | existingInTask.setOrgSite(param.getSourceStaNo()); |
| | | if (!taskService.updateById(existingInTask)) { |
| | | log.warn("更新入库任务的入库站点失败 - 任务编码:{}", existingInTask.getTaskCode()); |
| | | } |
| | | // 更新入库站点信息(如果与当前申请的站点不同) |
| | | if (StringUtils.isNotBlank(param.getSourceStaNo()) && |
| | | !param.getSourceStaNo().equals(existingInTask.getOrgSite())) { |
| | | log.info("更新入库任务的入库站点 - 任务编码:{},原站点:{},新站点:{}", |
| | | existingInTask.getTaskCode(), existingInTask.getOrgSite(), param.getSourceStaNo()); |
| | | existingInTask.setOrgSite(param.getSourceStaNo()); |
| | | if (!taskService.updateById(existingInTask)) { |
| | | log.warn("更新入库任务的入库站点失败 - 任务编码:{}", existingInTask.getTaskCode()); |
| | | } |
| | | |
| | | // 返回现有入库任务的信息 |
| | | InTaskMsgDto msgDto = new InTaskMsgDto(); |
| | | msgDto.setWorkNo(existingInTask.getTaskCode()); |
| | | msgDto.setTaskId(existingInTask.getId()); |
| | | msgDto.setLocNo(existingInTask.getTargLoc()); |
| | | msgDto.setSourceStaNo(existingInTask.getOrgSite()); |
| | | msgDto.setStaNo(existingInTask.getTargSite()); |
| | | return msgDto; |
| | | } else { |
| | | log.info("组托档没有任务编号,继续创建新任务 - 箱号:{}", param.getBarcode()); |
| | | } |
| | | } else { |
| | | log.info("未找到匹配的其他入库任务,继续创建新任务 - 箱号:{}", param.getBarcode()); |
| | | |
| | | // 直接返回已有任务信息,不再新建任务 |
| | | InTaskMsgDto msgDto = new InTaskMsgDto(); |
| | | msgDto.setWorkNo(existingInTask.getTaskCode()); |
| | | msgDto.setTaskId(existingInTask.getId()); |
| | | msgDto.setLocNo(existingInTask.getTargLoc()); |
| | | msgDto.setSourceStaNo(existingInTask.getOrgSite()); |
| | | msgDto.setStaNo(existingInTask.getTargSite()); |
| | | return msgDto; |
| | | } |
| | | |
| | | log.info("未找到该托盘号已有入库任务,继续创建新任务 - 箱号:{}", param.getBarcode()); |
| | | |
| | | // 生成任务编码 |
| | | String ruleCode = generateTaskCode(); |
| | |
| | | throw new RuntimeException(e); |
| | | } |
| | | |
| | | if (waitPakin == null) { |
| | | throw new CoolException("请检查组拖状态是否完成!!"); |
| | | } |
| | | // 创建并保存任务 |
| | | Task task = createTask(ruleCode, locNo.getLocNo(), waitPakin.getBarcode(), |
| | | deviceSite.getDeviceSite(), param.getSourceStaNo().toString(), param.getUser()); |
| | |
| | | return locNo; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * RCS 入库申请时若 barcode 无组托且配置启用:按 AUTO_FULL_OUT_MATNR_CODE 无订单组托并生成入库单,便于后续生成入库任务。 |
| | | */ |
| | | private void tryAutoPakinForBarcode(String barcode) { |
| | | Config enabledConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_PAKIN_ON_ASN_ENABLED)); |
| | | if (enabledConfig == null || !Boolean.parseBoolean(enabledConfig.getVal())) { |
| | | return; |
| | | } |
| | | Config matnrConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_FULL_OUT_MATNR_CODE)); |
| | | if (matnrConfig == null || StringUtils.isBlank(matnrConfig.getVal())) { |
| | | return; |
| | | } |
| | | // 二次确认:已有该 barcode 的组托则直接返回,由外层复用,避免重复请求生成多条入库单 |
| | | WaitPakin existing = waitPakinService.getOne(new LambdaQueryWrapper<WaitPakin>() |
| | | .eq(WaitPakin::getBarcode, barcode) |
| | | .in(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_DONE.val, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val)); |
| | | if (existing != null) { |
| | | log.info("[RCS入库申请-自动组托] barcode={} 已有组托,跳过自动组托", barcode); |
| | | return; |
| | | } |
| | | Config qtyConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_PAKIN_QTY)); |
| | | double autoQty = 1.0; |
| | | if (qtyConfig != null && StringUtils.isNotBlank(qtyConfig.getVal())) { |
| | | try { |
| | | autoQty = Double.parseDouble(qtyConfig.getVal().trim()); |
| | | if (autoQty <= 0) autoQty = 1.0; |
| | | } catch (NumberFormatException e) { |
| | | // ignore |
| | | } |
| | | } |
| | | String matnrCode = matnrConfig.getVal().trim(); |
| | | Matnr matnr = matnrService.getOne(new LambdaQueryWrapper<Matnr>().eq(Matnr::getCode, matnrCode)); |
| | | if (matnr == null) { |
| | | log.warn("[RCS入库申请-自动组托] 物料不存在: {}", matnrCode); |
| | | return; |
| | | } |
| | | List<PakinItem> pakinItems = new ArrayList<>(); |
| | | PakinItem pi = new PakinItem(); |
| | | pi.setMatnrId(matnr.getId()); |
| | | pi.setReceiptQty(autoQty); |
| | | pi.setAsnCode(null); |
| | | pi.setId(null); |
| | | pakinItems.add(pi); |
| | | WaitPakinParam param = new WaitPakinParam(); |
| | | param.setBarcode(barcode); |
| | | param.setItems(pakinItems); |
| | | WaitPakin waitPakin; |
| | | try { |
| | | waitPakin = mobileService.mergeItems(param, 1L); |
| | | } catch (Exception e) { |
| | | log.warn("[RCS入库申请-自动组托] 组托失败, barcode={}: {}", barcode, e.getMessage()); |
| | | throw new CoolException("barcode=" + barcode + ": " + e.getMessage()); |
| | | } |
| | | String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_ASN_ORDER, null); |
| | | if (StringUtils.isBlank(ruleCode)) { |
| | | log.warn("[RCS入库申请-自动组托] 入库单编码规则未配置"); |
| | | return; |
| | | } |
| | | // val orderWorkTypeOtherIn = OrderWorkType.ORDER_WORK_TYPE_OTHER_IN.type; |
| | | // .setWkType(orderWorkTypeOtherIn) |
| | | WkOrder order = new WkOrder(); |
| | | order.setCode(ruleCode) |
| | | .setType(OrderType.ORDER_IN.type) |
| | | .setExceStatus(AsnExceStatus.ASN_EXCE_STATUS_UN_EXCE.val) |
| | | .setAnfme(autoQty) |
| | | .setWorkQty(0.0) |
| | | .setQty(0.0) |
| | | .setCreateBy(1L) |
| | | .setUpdateBy(1L); |
| | | if (!asnOrderService.save(order)) { |
| | | throw new CoolException("入库主单保存失败"); |
| | | } |
| | | WkOrderItem orderItem = new WkOrderItem(); |
| | | orderItem.setOrderId(order.getId()) |
| | | .setOrderCode(order.getCode()) |
| | | .setMatnrId(matnr.getId()) |
| | | .setMatnrCode(matnr.getCode()) |
| | | .setMaktx(matnr.getName()) |
| | | .setAnfme(autoQty) |
| | | .setWorkQty(0.0) |
| | | .setQty(0.0) |
| | | .setStockUnit(matnr.getStockUnit() != null ? matnr.getStockUnit() : "个") |
| | | .setPurUnit(matnr.getPurUnit() != null ? matnr.getPurUnit() : "个") |
| | | .setFieldsIndex(matnr.getFieldsIndex()) |
| | | .setCreateBy(1L) |
| | | .setUpdateBy(1L); |
| | | if (!asnOrderItemService.save(orderItem)) { |
| | | throw new CoolException("入库明细保存失败"); |
| | | } |
| | | waitPakinItemService.update(new LambdaUpdateWrapper<WaitPakinItem>() |
| | | .eq(WaitPakinItem::getPakinId, waitPakin.getId()) |
| | | .set(WaitPakinItem::getAsnId, order.getId()) |
| | | .set(WaitPakinItem::getAsnCode, order.getCode()) |
| | | .set(WaitPakinItem::getAsnItemId, orderItem.getId())); |
| | | log.info("[RCS入库申请-自动组托] 已组托并生成入库单: {}, barcode: {}, 物料: {}, 数量: {}", order.getCode(), barcode, matnrCode, autoQty); |
| | | } |
| | | |
| | | /** |
| | | * 验证设备站点 |
| | |
| | | */ |
| | | private Task createTask(String ruleCode, String targetLoc, String barcode, |
| | | String targetSite, String sourceSiteNo, Long loginUserId) { |
| | | return createTask(ruleCode, targetLoc, barcode, targetSite, sourceSiteNo, loginUserId, TaskType.TASK_TYPE_IN.type); |
| | | } |
| | | |
| | | /** |
| | | * 创建并保存任务(支持指定任务类型,如空板入库) |
| | | */ |
| | | private Task createTask(String ruleCode, String targetLoc, String barcode, |
| | | String targetSite, String sourceSiteNo, Long loginUserId, Integer taskType) { |
| | | Task task = new Task(); |
| | | task.setTaskCode(ruleCode) |
| | | .setTaskStatus(TaskStsType.GENERATE_IN.id) |
| | | .setTaskType(TaskType.TASK_TYPE_IN.type) |
| | | .setTaskType(taskType != null ? taskType : TaskType.TASK_TYPE_IN.type) |
| | | .setWarehType(WarehType.WAREHOUSE_TYPE_CRN.val) |
| | | .setTargLoc(targetLoc) |
| | | .setBarcode(barcode) |
| | |
| | | if (!updated) { |
| | | throw new CoolException("库位预约失败!!"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 空板入库:RCS 申请时 full=true,无需组托,分配库位并创建 TASK_TYPE_EMPITY_IN 任务。 |
| | | * 需在设备站点中配置 type=10(空板入库)的站点路径。 |
| | | */ |
| | | private InTaskMsgDto createInTaskForEmptyPallet(String barcode, String staNo, Integer type) { |
| | | TaskInParam param = new TaskInParam(); |
| | | param.setBarcode(barcode); |
| | | param.setSourceStaNo(staNo); |
| | | param.setLocType1(type != null ? type : 1); |
| | | param.setIoType(TaskType.TASK_TYPE_EMPITY_IN.type); |
| | | param.setUser(1L); |
| | | |
| | | // 校验设备站点(需配置 type=10 空板入库的站点) |
| | | DeviceSite deviceSite = validateDeviceSite(param); |
| | | |
| | | // 检查该托盘号是否已有空板入库任务,有则复用 |
| | | Task existingInTask = taskService.getOne(new LambdaQueryWrapper<Task>() |
| | | .eq(Task::getBarcode, barcode) |
| | | .eq(Task::getTaskType, TaskType.TASK_TYPE_EMPITY_IN.type) |
| | | .orderByDesc(Task::getCreateTime) |
| | | .last("LIMIT 1")); |
| | | if (existingInTask != null) { |
| | | log.info("找到该托盘号已有空板入库任务,复用 - 任务编码:{},箱号:{}", existingInTask.getTaskCode(), barcode); |
| | | if (StringUtils.isNotBlank(staNo) && !staNo.equals(existingInTask.getOrgSite())) { |
| | | existingInTask.setOrgSite(staNo); |
| | | taskService.updateById(existingInTask); |
| | | } |
| | | InTaskMsgDto msgDto = new InTaskMsgDto(); |
| | | msgDto.setWorkNo(existingInTask.getTaskCode()); |
| | | msgDto.setTaskId(existingInTask.getId()); |
| | | msgDto.setLocNo(existingInTask.getTargLoc()); |
| | | msgDto.setSourceStaNo(existingInTask.getOrgSite()); |
| | | msgDto.setStaNo(existingInTask.getTargSite()); |
| | | return msgDto; |
| | | } |
| | | |
| | | // 该托盘已在库或出库中,不可重复申请空板入库 |
| | | List<Loc> inStock = locService.list(new LambdaQueryWrapper<Loc>().eq(Loc::getBarcode, barcode)); |
| | | if (!inStock.isEmpty()) { |
| | | throw new CoolException("barcode=" + barcode + ": 该托盘已在库,不可重复申请入库"); |
| | | } |
| | | Task outboundTask = taskService.getOne(new LambdaQueryWrapper<Task>() |
| | | .eq(Task::getBarcode, barcode) |
| | | .in(Task::getTaskType, Arrays.asList(TaskType.TASK_TYPE_OUT.type, TaskType.TASK_TYPE_EMPITY_OUT.type, |
| | | TaskType.TASK_TYPE_PICK_AGAIN_OUT.type, TaskType.TASK_TYPE_CHECK_OUT.type)) |
| | | .lt(Task::getTaskStatus, TaskStsType.COMPLETE_OUT.id)); |
| | | if (outboundTask != null) { |
| | | throw new CoolException("barcode=" + barcode + ": 该托盘出库中未完成,不可申请入库"); |
| | | } |
| | | |
| | | InTaskMsgDto locNo; |
| | | try { |
| | | locNo = getLocNo(param); |
| | | } catch (Exception e) { |
| | | throw new CoolException("获取空板入库库位失败:" + e.getMessage()); |
| | | } |
| | | if (locNo == null || StringUtils.isBlank(locNo.getLocNo())) { |
| | | throw new CoolException("未找到可用的空库位,请检查库区与设备站点配置(空板入库需配置 type=10 的站点)"); |
| | | } |
| | | String ruleCode = generateTaskCode(); |
| | | String targetSite = StringUtils.isNotBlank(deviceSite.getDeviceSite()) ? deviceSite.getDeviceSite() : staNo; |
| | | Task task = createTask(ruleCode, locNo.getLocNo(), barcode, targetSite, staNo, param.getUser(), TaskType.TASK_TYPE_EMPITY_IN.type); |
| | | updateLocStatus(task.getTargLoc(), barcode); |
| | | locNo.setWorkNo(ruleCode); |
| | | locNo.setTaskId(task.getId()); |
| | | log.info("[空板入库] 已创建任务: {}, 库位: {}, 料箱: {}", ruleCode, locNo.getLocNo(), barcode); |
| | | return locNo; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public R allocateLocation(String barcode, String staNo, Integer type) { |
| | | public R allocateLocation(String barcode, String staNo, Integer type, Boolean full) { |
| | | log.info("========== 开始申请入库任务,分配库位 =========="); |
| | | log.info("料箱码:{},入库站点:{},入库类型:{}", barcode, staNo, type); |
| | | log.info("料箱码:{},入库站点:{},入库类型:{},空板:{}", barcode, staNo, type, full); |
| | | |
| | | // full=true 时走空板入库(无需组托);否则走普通入库(需组托或自动组托) |
| | | if (Boolean.TRUE.equals(full)) { |
| | | InTaskMsgDto msgDto = createInTaskForEmptyPallet(barcode, staNo, type); |
| | | JSONObject result = new JSONObject(); |
| | | result.put("locNo", msgDto.getLocNo()); |
| | | result.put("batchNo", msgDto.getWorkNo()); |
| | | result.put("taskNo", msgDto.getWorkNo()); |
| | | return R.ok(result); |
| | | } |
| | | |
| | | // 构建 TaskInParam 参数,与 /wcs/create/in/task 接口参数一致 |
| | | TaskInParam param = new TaskInParam(); |