| | |
| | | throw new CoolException("库存不存在"); |
| | | } |
| | | } |
| | | /** |
| | | * 检查深库位前方是否堵塞(按 locGroupAscOrder 配置的分组和顺序检查) |
| | | * 只检查同一 bay + lev + 当前分组 rowList 内,前方位置是否有货(F) 或 入库任务 |
| | | * |
| | | * @param locMast 当前库位主档 |
| | | * @param locNo 当前库位号 |
| | | * @param locGroupAscOrder 出库优先分组配置 |
| | | * @throws CoolException 如果前方有货或入库任务 |
| | | */ |
| | | private void checkDeepPositionBlocking(LocMast locMast, String locNo, List<LocGroupOrder> locGroupAscOrder) { |
| | | // 查找当前 row 所属分组 |
| | | LocGroupOrder currentGroup = locGroupAscOrder.stream() |
| | | .filter(g -> g.getRowList().contains(locMast.getRow1())) |
| | | .findFirst() |
| | | .orElseThrow(() -> new CoolException("当前排不在任何出库优先分组中,无法进行深库位检查: row=" + locMast.getRow1())); |
| | | |
| | | List<Integer> orderedRows = currentGroup.getRowList(); |
| | | |
| | | // 查询同一 bay + lev + 本组 rowList 内的所有库位 |
| | | List<LocMast> sameBayLevLocs = locMastService.selectList(new EntityWrapper<LocMast>() |
| | | .eq("bay1", locMast.getBay1()) |
| | | .eq("lev1", locMast.getLev1()) |
| | | .in("row1", orderedRows)); |
| | | |
| | | // 按配置顺序重新排列 |
| | | List<LocMast> orderedLocs = orderedRows.stream() |
| | | .map(row -> sameBayLevLocs.stream() |
| | | .filter(lm -> lm.getRow1().equals(row)) |
| | | .findFirst() |
| | | .orElse(null)) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 遍历检查前方 |
| | | boolean foundSelf = false; |
| | | for (LocMast prevLoc : orderedLocs) { |
| | | if (prevLoc.getLocNo().equals(locNo)) { |
| | | foundSelf = true; |
| | | break; // 到达自己位置,停止 |
| | | } |
| | | |
| | | // 前方有货(F状态) |
| | | if ("F".equals(prevLoc.getLocSts())) { |
| | | throw new CoolException(locNo + " 的前方位置 " + prevLoc.getLocNo() + " 有货,禁止出库"); |
| | | } |
| | | |
| | | // 前方有入库任务(假设 io_type=100 为入库,根据实际调整) |
| | | // 这里只查待发送的任务(wrk_sts=0),可根据需要加 wrk_sts=1 等 |
| | | WrkMast frontTask = wrkMastService.selectOne( |
| | | new EntityWrapper<WrkMast>() |
| | | .eq("source_loc_no", prevLoc.getLocNo()) // 入库任务的源位置 |
| | | .eq("io_type", 100) // 入库类型(请确认你的系统定义) |
| | | .eq("wrk_sts", 0L) // 待发送 |
| | | ); |
| | | |
| | | if (frontTask != null) { |
| | | throw new CoolException(locNo + " 的前方位置 " + prevLoc.getLocNo() + " 存在入库任务,禁止出库"); |
| | | } |
| | | } |
| | | |
| | | if (!foundSelf) { |
| | | throw new CoolException("分组内未找到目标库位,请检查数据一致性: " + locNo); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional |
| | | public void stockOut(BasDevp staNo, List<LocDetlDto> locDetlDtos, IoWorkType ioWorkType, Long userId) { |
| | | Date now = new Date(); |
| | | // 合并同类项 |
| | | |
| | | // 从配置获取分组(和空板出库保持一致) |
| | | List<LocGroupOrder> locGroupAscOrder = slaveProperties.getLocGroupAscOrder(); |
| | | |
| | | // 合并同类项(相同 locNo 的明细合并) |
| | | Set<String> locNos = new HashSet<>(); |
| | | List<OutLocDto> dtos = new ArrayList<>(); |
| | | for (LocDetlDto locDetlDto : locDetlDtos) { |
| | |
| | | dtos.add(new OutLocDto(locNo, locDetlDto)); |
| | | } |
| | | } |
| | | |
| | | Integer ioType = null; |
| | | |
| | | // 按列和层分组库位 |
| | | // 按列和层分组库位(bay + lev) |
| | | Map<String, List<OutLocDto>> locGroups = dtos.stream() |
| | | .collect(Collectors.groupingBy(dto -> { |
| | | String column = dto.getLocNo().substring(3, 6); // 列号 |
| | | String level = dto.getLocNo().substring(6); // 层号 |
| | | return column + "-" + level; // 组合成唯一的组标识 |
| | | return column + "-" + level; |
| | | })); |
| | | |
| | | // 遍历每组 |
| | | for (Map.Entry<String, List<OutLocDto>> entry : locGroups.entrySet()) { |
| | | String groupKey = entry.getKey(); // 组标识,例如 "003-02" 表示第3列第2层 |
| | | List<OutLocDto> groupDtos = entry.getValue(); |
| | | |
| | | // 组内优先级重新从100开始 |
| | | // 组内优先级重新从100开始(原有逻辑) |
| | | double priority = 100; |
| | | |
| | | // 排序组内库位(排号倒序) |
| | | // 排序组内库位(排号倒序,原有逻辑) |
| | | groupDtos.sort(Comparator.comparing((OutLocDto dto) -> Integer.valueOf(dto.getLocNo().substring(0, 2))).reversed()); |
| | | |
| | | for (OutLocDto dto : groupDtos) { |
| | | String locNo = dto.getLocNo(); |
| | | |
| | | // 深库位规则检查,仅检查当前列和层内的前库位 |
| | | int currentRow = Integer.valueOf(locNo.substring(0, 2)); |
| | | for (int i = currentRow + 1; i <= 5; i++) { // 深库位为 1-5 排 |
| | | String frontLoc = String.format("%02d%s", i, locNo.substring(2)); |
| | | LocMast locMastFront = locMastService.selectOne(new EntityWrapper<LocMast>() |
| | | .eq("loc_no", frontLoc).eq("loc_sts", "F")); |
| | | if (!Cools.isEmpty(locMastFront)) { |
| | | throw new CoolException(locNo + " 的前库位 " + frontLoc + " 有货"); |
| | | } |
| | | |
| | | // 判断前库位是否有入库任务 |
| | | WrkMast wrkMastFront = wrkMastService.selectOne(new EntityWrapper<WrkMast>().eq("loc_no", frontLoc)); |
| | | if (!Cools.isEmpty(wrkMastFront)) { |
| | | throw new CoolException(locNo + " 的前库位 " + frontLoc + " 有入库任务"); |
| | | } |
| | | } |
| | | |
| | | // 计算优先级 |
| | | dto.setPriority(priority); |
| | | priority--; |
| | | |
| | | // 获取库位 |
| | | LocMast locMast = locMastService.selectById(dto.getLocNo()); |
| | | // 获取库位信息(提前获取,用于检查和后续使用) |
| | | LocMast locMast = locMastService.selectById(locNo); |
| | | if (Cools.isEmpty(locMast)) { |
| | | throw new CoolException(dto.getLocNo() + "库位不存在"); |
| | | throw new CoolException(locNo + "库位不存在"); |
| | | } |
| | | if (!locMast.getLocSts().equals("F")) { |
| | | throw new CoolException(dto.getLocNo() + "托盘非在库状态"); |
| | | throw new CoolException(locNo + "托盘非在库状态"); |
| | | } |
| | | |
| | | // ===================================== |
| | | // 深库位前方堵塞检查(新逻辑,按分组顺序) |
| | | checkDeepPositionBlocking(locMast, locNo, locGroupAscOrder); |
| | | // ===================================== |
| | | |
| | | // 计算优先级(原有逻辑) |
| | | dto.setPriority(priority); |
| | | priority--; |
| | | |
| | | // 判断入出库类型 |
| | | if (ioWorkType == null) { |
| | |
| | | wrkMast.setCrnNo(locMast.getCrnNo()); |
| | | wrkMast.setSourceStaNo(staDesc.getCrnStn()); |
| | | wrkMast.setStaNo(staDesc.getStnNo()); |
| | | wrkMast.setSourceLocNo(dto.getLocNo()); |
| | | wrkMast.setSourceLocNo(locNo); |
| | | wrkMast.setFullPlt("Y"); |
| | | wrkMast.setPicking(pick); |
| | | wrkMast.setExitMk("N"); |
| | |
| | | wrkMast.setModiUser(userId); |
| | | wrkMast.setModiTime(now); |
| | | if (!wrkMastService.insert(wrkMast)) { |
| | | throw new CoolException("保存工作档失败,出库库位号:" + dto.getLocNo()); |
| | | throw new CoolException("保存工作档失败,出库库位号:" + locNo); |
| | | } |
| | | |
| | | // 生成工作档明细 |
| | |
| | | } |
| | | |
| | | // 修改库位状态 |
| | | locMast = locMastService.selectById(dto.getLocNo()); |
| | | locMast = locMastService.selectById(locNo); // 重新获取最新状态 |
| | | if (locMast.getLocSts().equals("F")) { |
| | | locMast.setLocSts(ioType == 101 ? "R" : "P"); |
| | | locMast.setModiUser(userId); |
| | | locMast.setModiTime(now); |
| | | if (!locMastService.updateById(locMast)) { |
| | | throw new CoolException("预约库位状态失败,库位号:" + dto.getLocNo()); |
| | | throw new CoolException("预约库位状态失败,库位号:" + locNo); |
| | | } |
| | | } else { |
| | | throw new CoolException(dto.getLocNo() + "库位不是在库状态"); |
| | | throw new CoolException(locNo + "库位不是在库状态"); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | return dto.getLocNo(); |
| | | } |
| | | /** |
| | | * 检查空板出库分组内选中连续段的双向前方清空情况,并返回出库方向与排序后的库位列表 |
| | | * |
| | | * @param selectedLocNos 选中的库位号列表 |
| | | * @param locGroupAscOrder 出库分组配置 |
| | | * @return LockingCheckResultParam 包含校验结果、方向、排序后库位列表 |
| | | */ |
| | | private LockingCheckResultParam checkEmptyPlateBlocking( |
| | | List<String> selectedLocNos, |
| | | List<LocGroupOrder> locGroupAscOrder) { |
| | | |
| | | if (Cools.isEmpty(selectedLocNos)) { |
| | | return LockingCheckResultParam.success(Collections.emptyList()); |
| | | } |
| | | |
| | | // 1. 查询所有选中的库位信息 |
| | | List<LocMast> selectedMasts = locMastService.selectList( |
| | | new EntityWrapper<LocMast>().in("loc_no", selectedLocNos) |
| | | ); |
| | | |
| | | if (selectedMasts.size() != selectedLocNos.size()) { |
| | | return LockingCheckResultParam.fail("部分选中库位不存在或数据异常"); |
| | | } |
| | | |
| | | // 2. 按分组聚合选中的库位(支持多分组) |
| | | Map<LocGroupOrder, List<LocMast>> groupSelected = new HashMap<>(); |
| | | for (LocMast lm : selectedMasts) { |
| | | LocGroupOrder group = locGroupAscOrder.stream() |
| | | .filter(g -> g.getRowList().contains(lm.getRow1())) |
| | | .findFirst() |
| | | .orElseThrow(() -> new CoolException("排不在出库分组配置中: row=" + lm.getRow1())); |
| | | |
| | | groupSelected.computeIfAbsent(group, k -> new ArrayList<>()).add(lm); |
| | | } |
| | | |
| | | // 由于通常一次请求在一个分组内,这里取第一个分组的结果 |
| | | // 如果需要支持多分组同时出库,可改为返回 Map<LocGroupOrder, LockingCheckResultParam> |
| | | LockingCheckResultParam result = null; |
| | | |
| | | for (Map.Entry<LocGroupOrder, List<LocMast>> entry : groupSelected.entrySet()) { |
| | | LocGroupOrder group = entry.getKey(); |
| | | List<LocMast> selected = entry.getValue(); |
| | | |
| | | List<Integer> fullRows = group.getRowList(); // 如 [3,4,5,6,7,8,9,10] |
| | | |
| | | // 获取选中的 row,并按分组顺序排序 |
| | | List<Integer> selectedRows = selected.stream() |
| | | .map(LocMast::getRow1) |
| | | .distinct() |
| | | .sorted(Comparator.comparingInt(fullRows::indexOf)) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 检查是否重复或无效 |
| | | if (selectedRows.size() != selected.size()) { |
| | | return LockingCheckResultParam.fail("选中库位存在重复或无效排"); |
| | | } |
| | | |
| | | int minIndex = fullRows.indexOf(selectedRows.get(0)); |
| | | int maxIndex = fullRows.indexOf(selectedRows.get(selectedRows.size() - 1)); |
| | | |
| | | // 1. 必须是连续段(无缺口) |
| | | if (maxIndex - minIndex + 1 != selectedRows.size()) { |
| | | return LockingCheckResultParam.fail( |
| | | "选中排必须连续,无缺口。从 " + fullRows.get(minIndex) + " 到 " + fullRows.get(maxIndex) |
| | | ); |
| | | } |
| | | |
| | | // 2. 检查左前方(正序方向:从左到右,前方是索引小的位置) |
| | | boolean leftClear = true; |
| | | for (int i = 0; i < minIndex; i++) { |
| | | LocMast prev = getLocMastByRow(fullRows.get(i), selected.get(0).getBay1(), selected.get(0).getLev1()); |
| | | if (prev != null && ("D".equals(prev.getLocSts()) || "F".equals(prev.getLocSts()))) { |
| | | leftClear = false; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | // 3. 检查右前方(倒序方向:从右到左,前方是索引大的位置) |
| | | boolean rightClear = true; |
| | | for (int i = maxIndex + 1; i < fullRows.size(); i++) { |
| | | LocMast prev = getLocMastByRow(fullRows.get(i), selected.get(0).getBay1(), selected.get(0).getLev1()); |
| | | if (prev != null && ("D".equals(prev.getLocSts()) || "F".equals(prev.getLocSts()))) { |
| | | rightClear = false; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | // 4. 至少有一侧清空才允许出库 |
| | | if (!leftClear && !rightClear) { |
| | | return LockingCheckResultParam.fail( |
| | | "选中段 " + fullRows.get(minIndex) + "~" + fullRows.get(maxIndex) + |
| | | " 两侧前方都有空板/故障,无法出库(正序或倒序方向都堵塞)" |
| | | ); |
| | | } |
| | | |
| | | // 5. 选中段内所有库位必须是 D 状态 |
| | | for (LocMast lm : selected) { |
| | | if (!"D".equals(lm.getLocSts())) { |
| | | return LockingCheckResultParam.fail("选中库位非空板状态: " + lm.getLocNo()); |
| | | } |
| | | } |
| | | |
| | | // 6. 决定出库方向和排序顺序 |
| | | String direction; |
| | | List<LocMast> sortedSelected; |
| | | |
| | | // 优先选择正序(如果两侧都清空,默认正序) |
| | | if (leftClear) { |
| | | direction = "ASC"; |
| | | sortedSelected = selected.stream() |
| | | .sorted(Comparator.comparingInt(m -> fullRows.indexOf(m.getRow1()))) |
| | | .collect(Collectors.toList()); |
| | | } else { |
| | | direction = "DESC"; |
| | | sortedSelected = selected.stream() |
| | | .sorted(Comparator.comparingInt(m -> -fullRows.indexOf(m.getRow1()))) // 倒序 |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | result = LockingCheckResultParam.success(direction, sortedSelected); |
| | | } |
| | | |
| | | // 如果没有分组(理论上不会发生),返回默认成功 |
| | | return result != null ? result : LockingCheckResultParam.success(Collections.emptyList()); |
| | | } |
| | | |
| | | // 辅助方法:根据 row 获取 LocMast |
| | | private LocMast getLocMastByRow(Integer row, Integer bay1, Integer lev1) { |
| | | return locMastService.selectOne(new EntityWrapper<LocMast>() |
| | | .eq("bay1", bay1) |
| | | .eq("lev1", lev1) |
| | | .eq("row1", row)); |
| | | } |
| | | @Override |
| | | @Transactional |
| | | public void emptyPlateOut(EmptyPlateOutParam param, Long userId) { |
| | | if (Cools.isEmpty(param.getOutSite())) { |
| | | throw new CoolException("站点不存在"); |
| | | } |
| | | for (String locNo : param.getLocNos()) { |
| | | |
| | | // 使用新的出库专用分组配置 |
| | | List<LocGroupOrder> locGroupAscOrder = slaveProperties.getLocGroupAscOrderOut(); |
| | | |
| | | // 1. 先进行整体阻塞检查(包含连续性 + 双向前方清空校验) |
| | | LockingCheckResultParam checkResult = checkEmptyPlateBlocking(param.getLocNos(), locGroupAscOrder); |
| | | |
| | | if (!checkResult.isSuccess()) { |
| | | throw new CoolException(checkResult.getErrorMessage()); |
| | | } |
| | | |
| | | // 2. 获取按出库方向排序好的库位列表 |
| | | List<LocMast> orderedLocMasts = checkResult.getSortedSelected(); |
| | | |
| | | // 3. 优先级基础参数(数值越大优先级越高) |
| | | double BASE_PRI = 10.0; // 最低优先级组基准(数值最大) |
| | | double GROUP_STEP = 100.0; // 组间差距 |
| | | double IN_GROUP_STEP = 1.0; // 组内位置差距 |
| | | |
| | | Date now = new Date(); |
| | | |
| | | // 4. 逐个处理排序后的库位,生成出库任务 |
| | | for (int index = orderedLocMasts.size(); index > 0; index--) { |
| | | LocMast locMast = orderedLocMasts.get(index); |
| | | String locNo = locMast.getLocNo(); |
| | | |
| | | // 获取工作号 |
| | | int workNo = commonService.getWorkNo(WorkNoType.PAKOUT.type); |
| | | // 获取库位 |
| | | LocMast locMast = locMastService.selectById(locNo); |
| | | if (Cools.isEmpty(locMast)) { |
| | | throw new CoolException(locNo+"库位不存在"); |
| | | } |
| | | if (!locMast.getLocSts().equals("D")){ |
| | | throw new CoolException("所选库位存在状态不为D的库位,库位号:"+locMast.getLocNo()+" 、当前状态:"+locMast.getLocSts()+"-"+locMast.getLocSts$()); |
| | | } |
| | | boolean res1 = true; |
| | | if(param.getOutSite()==100){ |
| | | res1 = false; |
| | | } |
| | | List<LocMast> locMasts = locMastService.selectList(new EntityWrapper<LocMast>() |
| | | .eq("bay1", locMast.getBay1()) |
| | | .eq("lev1",locMast.getLev1()) |
| | | .orderBy("row1",res1)); |
| | | for (LocMast locMast1 : locMasts) { |
| | | if (locMast1.getLocNo().equals(locMast.getLocNo())) { |
| | | break; |
| | | } |
| | | if (locMast1.getLocSts().equals("D") || locMast1.getLocSts().equals("F")) { |
| | | throw new CoolException(locNo+"库位堵塞,禁止出库"); |
| | | } |
| | | } |
| | | |
| | | // 获取源站 |
| | | Wrapper<StaDesc> wrapper = new EntityWrapper<StaDesc>() |
| | | .eq("type_no", 110) |
| | |
| | | StaDesc staDesc = staDescService.selectOne(wrapper); |
| | | Integer sourceStaNo = staDesc.getCrnStn(); |
| | | if (Cools.isEmpty(sourceStaNo)) { |
| | | throw new CoolException("检索源站失败"); |
| | | throw new CoolException("检索源站失败,库位:" + locNo); |
| | | } |
| | | Date now = new Date(); |
| | | // 保存工作档 |
| | | |
| | | // 计算动态优先级(组优先级 + 组内顺序偏移) |
| | | // 这里假设所有选中的库位在同一个分组(多分组可再细分) |
| | | int groupIndex = locGroupAscOrder.indexOf( |
| | | locGroupAscOrder.stream() |
| | | .filter(g -> g.getRowList().contains(locMast.getRow1())) |
| | | .findFirst() |
| | | .orElse(null) |
| | | ); |
| | | |
| | | if (groupIndex == -1) { |
| | | throw new CoolException("分组索引异常: " + locNo); |
| | | } |
| | | |
| | | double groupBasePri = BASE_PRI + (locGroupAscOrder.size() + 1 - groupIndex) * GROUP_STEP; |
| | | |
| | | // 组内偏移:按出库顺序(index越小越先出,优先级越高 → 数值越大) |
| | | // 如果是 DESC 方向,index 已经是倒序的,所以直接用 |
| | | double inGroupOffset = index * IN_GROUP_STEP; |
| | | |
| | | double ioPri = groupBasePri + inGroupOffset; // 数值越大优先级越高 |
| | | |
| | | // 生成工作档 |
| | | WrkMast wrkMast = new WrkMast(); |
| | | wrkMast.setWrkNo(workNo); |
| | | wrkMast.setIoTime(now); |
| | | wrkMast.setWrkSts(0L); // 工作状态:0.待发送 |
| | | wrkMast.setIoType(110); // 入出库状态: 110.空板出库 |
| | | wrkMast.setIoPri(10D); |
| | | wrkMast.setSourceStaNo(sourceStaNo); // 源站 |
| | | wrkMast.setStaNo(param.getOutSite()); // 目标站 |
| | | wrkMast.setWrkSts(0L); // 待发送 |
| | | wrkMast.setIoType(110); // 空板出库 |
| | | wrkMast.setIoPri(ioPri); |
| | | wrkMast.setSourceStaNo(sourceStaNo); |
| | | wrkMast.setStaNo(param.getOutSite()); |
| | | wrkMast.setCrnNo(locMast.getCrnNo()); |
| | | wrkMast.setSourceLocNo(locNo); // 源库位 |
| | | wrkMast.setFullPlt("N"); // 满板:Y |
| | | wrkMast.setPicking("N"); // 拣料 |
| | | wrkMast.setExitMk("N"); // 退出 |
| | | wrkMast.setEmptyMk("Y"); // 空板 |
| | | wrkMast.setSourceLocNo(locNo); |
| | | wrkMast.setFullPlt("N"); |
| | | wrkMast.setPicking("N"); |
| | | wrkMast.setExitMk("N"); |
| | | wrkMast.setEmptyMk("Y"); |
| | | wrkMast.setLinkMis("N"); |
| | | wrkMast.setAppeUser(userId); |
| | | wrkMast.setAppeTime(now); |
| | | wrkMast.setModiUser(userId); |
| | | wrkMast.setModiTime(now); |
| | | boolean res = wrkMastService.insert(wrkMast); |
| | | if (!res) { |
| | | throw new CoolException("保存工作档失败"); |
| | | |
| | | if (!wrkMastService.insert(wrkMast)) { |
| | | throw new CoolException("保存工作档失败,库位:" + locNo); |
| | | } |
| | | // 更新库位状态 D.空板 -> R.出库预约 |
| | | if (locMast.getLocSts().equals("D")){ |
| | | |
| | | // 更新库位状态 D → R(出库预约) |
| | | if ("D".equals(locMast.getLocSts())) { |
| | | locMast.setLocSts("R"); |
| | | locMast.setModiUser(userId); |
| | | locMast.setModiTime(now); |
| | | if (!locMastService.updateById(locMast)) { |
| | | throw new CoolException("更新库位状态失败"); |
| | | throw new CoolException("更新库位状态失败,库位:" + locNo); |
| | | } |
| | | } else { |
| | | throw new CoolException("库位状态异常,非空板状态:" + locNo); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional |
| | | public WrkMast emptyPlateOut(EmptyPlateOutParam param) { |