| | |
| | | import com.core.exception.CoolException; |
| | | import com.zy.asrs.entity.*; |
| | | import com.zy.asrs.entity.param.*; |
| | | import com.zy.asrs.entity.result.FindLocNoAttributeVo; |
| | | import com.zy.asrs.entity.result.WrkCancel; |
| | | import com.zy.asrs.service.*; |
| | | import com.zy.asrs.utils.Utils; |
| | |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.*; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.concurrent.atomic.AtomicReference; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | |
| | | throw new CoolException("库存不存在"); |
| | | } |
| | | } |
| | | private boolean isInNormalRule(LocMast lm) { |
| | | List<LocGroupOrder> locGroupAscOrder = slaveProperties.getLocGroupAscOrder(); |
| | | if (locGroupAscOrder == null || locGroupAscOrder.isEmpty()) { |
| | | return false; |
| | | } |
| | | Integer row = lm.getRow1(); |
| | | if (row == null) return false; |
| | | |
| | | return locGroupAscOrder.stream() |
| | | .anyMatch(group -> |
| | | group.getRowList() != null && |
| | | group.getRowList().contains(row) |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * 检查正常库位前方是否堵塞(深库位规则:前方排是否有货或入库任务) |
| | | * |
| | | * 判断依据: |
| | | * 1. 只检查属于出库分组规则内的库位(通过分组的 rowList 确定范围) |
| | | * 2. 前方 = 分组中索引大于当前库位的排(假设分组 rowList 已按出库顺序排列,大索引 = 前方更深) |
| | | * 3. 堵塞条件:前方有货(F状态)或有入库任务 |
| | | * |
| | | * @param normalMasts 需要检查的正常库位列表(已通过 isInNormalRule 过滤) |
| | | * @return true = 前方有堵塞(需要补齐),false = 前方清空或无需检查 |
| | | */ |
| | | private boolean checkDeepLocationBlocked(List<LocMast> normalMasts) { |
| | | if (normalMasts == null || normalMasts.isEmpty()) { |
| | | return false; |
| | | } |
| | | |
| | | // 获取出库分组配置 |
| | | List<LocGroupOrder> locGroupAscOrder = slaveProperties.getLocGroupAscOrder(); |
| | | if (locGroupAscOrder == null || locGroupAscOrder.isEmpty()) { |
| | | return false; // 无配置时默认不检查 |
| | | } |
| | | |
| | | // 假设所有 normalMasts 在同一个分组(常见情况,若多分组可循环处理) |
| | | LocMast representative = normalMasts.get(0); |
| | | LocGroupOrder group = locGroupAscOrder.stream() |
| | | .filter(g -> g.getRowList() != null && g.getRowList().contains(representative.getRow1())) |
| | | .findFirst() |
| | | .orElse(null); |
| | | |
| | | if (group == null || group.getRowList() == null || group.getRowList().isEmpty()) { |
| | | return false; // 不在任何分组,不检查 |
| | | } |
| | | |
| | | List<Integer> fullRows = group.getRowList(); // 分组内所有排号列表(按出库顺序排列) |
| | | |
| | | // 遍历每个正常库位 |
| | | for (LocMast lm : normalMasts) { |
| | | Integer currentRow = lm.getRow1(); |
| | | Integer bay1 = lm.getBay1(); |
| | | Integer lev1 = lm.getLev1(); |
| | | |
| | | if (currentRow == null || bay1 == null || lev1 == null) { |
| | | continue; |
| | | } |
| | | |
| | | // 在分组 rowList 中找到当前排的索引 |
| | | int currentIndex = fullRows.indexOf(currentRow); |
| | | if (currentIndex < 0) { |
| | | continue; // 当前排不在分组内,跳过 |
| | | } |
| | | |
| | | // 前方 = 分组中索引更大的位置(假设分组 rowList 从前到后排列,大索引 = 更深) |
| | | // 如果你的分组列表是倒序的(从后到前),则需改为 currentIndex - 1 到 0 |
| | | for (int i = currentIndex + 1; i < fullRows.size(); i++) { |
| | | Integer frontRow = fullRows.get(i); |
| | | LocMast front = getLocMastByRow(frontRow, bay1, lev1); |
| | | if (front == null) { |
| | | continue; |
| | | } |
| | | |
| | | // 有货(F状态) → 堵塞 |
| | | if ("F".equals(front.getLocSts())) { |
| | | return true; |
| | | } |
| | | |
| | | WrkMast frontTask = wrkMastService.selectOne( |
| | | new EntityWrapper<WrkMast>() |
| | | .eq("source_loc_no", front.getLocNo()) |
| | | .in("loc_sts", Arrays.asList("S", "Q")) // 支持 loc_sts 为 S 或 Q |
| | | ); |
| | | |
| | | if (frontTask != null) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 所有正常库位前方都清空(或无前方) |
| | | return false; |
| | | } |
| | | @Override |
| | | @Transactional |
| | | public void stockOut(BasDevp staNo, List<LocDetlDto> locDetlDtos, IoWorkType ioWorkType, Long userId) { |
| | | Date now = new Date(); |
| | | // 合并同类项 |
| | | |
| | | // 保留:合并同类项(同一库位合并明细) |
| | | Set<String> locNos = new HashSet<>(); |
| | | List<OutLocDto> dtos = new ArrayList<>(); |
| | | for (LocDetlDto locDetlDto : locDetlDtos) { |
| | |
| | | dtos.add(new OutLocDto(locNo, locDetlDto)); |
| | | } |
| | | } |
| | | Integer ioType = null; |
| | | // 使用出库专用分组配置 |
| | | List<LocGroupOrder> locGroupAscOrder = slaveProperties.getLocGroupAscOrder(); |
| | | // 确定正常出库类型(和原来保持类似) |
| | | int ioTypeNormal = 101; // 默认整托出库 |
| | | if (ioWorkType != null && ioWorkType.equals(IoWorkType.CHECK_OUT)) { |
| | | ioTypeNormal = 107; |
| | | } else if (!dtos.isEmpty() && !dtos.get(0).isAll()) { |
| | | ioTypeNormal = 103; // 部分出库 |
| | | } |
| | | |
| | | // 按列和层分组库位 |
| | | 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; // 组合成唯一的组标识 |
| | | })); |
| | | // 1. 查询所有选中的库位主信息 |
| | | List<LocMast> allSelectedMasts = locMastService.selectList( |
| | | new EntityWrapper<LocMast>().in("loc_no", locNos) |
| | | ); |
| | | |
| | | // 遍历每组 |
| | | for (Map.Entry<String, List<OutLocDto>> entry : locGroups.entrySet()) { |
| | | String groupKey = entry.getKey(); // 组标识,例如 "003-02" 表示第3列第2层 |
| | | List<OutLocDto> groupDtos = entry.getValue(); |
| | | if (allSelectedMasts.size() != locNos.size()) { |
| | | throw new CoolException("部分选中库位不存在或数据异常"); |
| | | } |
| | | |
| | | // 组内优先级重新从100开始 |
| | | double priority = 100; |
| | | // 2. 区分正常库位(需要严格深库位检查)与补充库位 |
| | | List<LocMast> normalMasts = new ArrayList<>(); |
| | | List<LocMast> supplementMasts = new ArrayList<>(); |
| | | |
| | | // 排序组内库位(排号倒序) |
| | | groupDtos.sort(Comparator.comparing((OutLocDto dto) -> Integer.valueOf(dto.getLocNo().substring(0, 2))).reversed()); |
| | | // 假设我们有某种“分组规则”(如按列层或排范围),这里简化用一个示例判断 |
| | | // 你可以替换成实际的规则(如 getLocGroupOrderOut() 或其他) |
| | | for (LocMast lm : allSelectedMasts) { |
| | | boolean isNormal = isInNormalRule(lm); // ← 你需要实现这个判断方法 |
| | | if (isNormal) { |
| | | normalMasts.add(lm); |
| | | } else { |
| | | supplementMasts.add(lm); |
| | | } |
| | | } |
| | | |
| | | for (OutLocDto dto : groupDtos) { |
| | | String locNo = dto.getLocNo(); |
| | | // 3. 对正常库位进行深库位前方检查(类似之前的连续段 + 清空) |
| | | AtomicReference<Boolean> isLeftSideSupplement = new AtomicReference<>(false); |
| | | |
| | | // 深库位规则检查,仅检查当前列和层内的前库位 |
| | | 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 + " 有货"); |
| | | } |
| | | if (!normalMasts.isEmpty()) { |
| | | // 这里模拟深库位前方检查(你可以替换成你实际的检查方法) |
| | | boolean hasBlockage = checkDeepLocationBlocked(normalMasts); |
| | | |
| | | // 判断前库位是否有入库任务 |
| | | WrkMast wrkMastFront = wrkMastService.selectOne(new EntityWrapper<WrkMast>().eq("loc_no", frontLoc)); |
| | | if (!Cools.isEmpty(wrkMastFront)) { |
| | | throw new CoolException(locNo + " 的前库位 " + frontLoc + " 有入库任务"); |
| | | } |
| | | if (hasBlockage) { |
| | | // 前方堵塞 → 自动补充最少一侧 |
| | | supplementBothSidesBlocked(normalMasts, locGroupAscOrder, supplementMasts, isLeftSideSupplement); |
| | | } |
| | | } |
| | | |
| | | // 4. 合并所有要出库的库位 |
| | | List<LocMast> allMasts = new ArrayList<>(); |
| | | allMasts.addAll(normalMasts); |
| | | allMasts.addAll(supplementMasts); |
| | | |
| | | if (allMasts.isEmpty()) { |
| | | throw new CoolException("没有有效的出库库位"); |
| | | } |
| | | |
| | | // 5. 统一按排号(row)倒序排序(高排先出) |
| | | List<LocMast> sortedAll = allMasts.stream() |
| | | .sorted(Comparator.comparing(LocMast::getRow1, Comparator.reverseOrder())) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 6. 生成任务(从后往前遍历) |
| | | double basePriority = 100.0; |
| | | for (int index = sortedAll.size() - 1; index >= 0; index--) { |
| | | LocMast locMast = sortedAll.get(index); |
| | | String locNo = locMast.getLocNo(); |
| | | |
| | | boolean isSupplement = supplementMasts.contains(locMast); |
| | | int ioType = isSupplement ? 11 : ioTypeNormal; |
| | | // 优先级计算 |
| | | double priority; |
| | | if (isSupplement) { |
| | | if (Boolean.TRUE.equals(isLeftSideSupplement.get())) { |
| | | // 左侧补充 → 晚出 |
| | | priority = basePriority - index * 1.0; |
| | | } else { |
| | | // 右侧补充 → 早出 |
| | | priority = basePriority + index * 1.0; |
| | | } |
| | | |
| | | // 计算优先级 |
| | | dto.setPriority(priority); |
| | | priority--; // 下一个任务优先级递增 |
| | | |
| | | // 获取库位 |
| | | LocMast locMast = locMastService.selectById(dto.getLocNo()); |
| | | if (Cools.isEmpty(locMast)) { |
| | | throw new CoolException(dto.getLocNo() + "库位不存在"); |
| | | } |
| | | if (!locMast.getLocSts().equals("F")) { |
| | | throw new CoolException(dto.getLocNo() + "托盘非在库状态"); |
| | | } |
| | | |
| | | // 判断入出库类型 |
| | | if (ioWorkType == null) { |
| | | ioType = dto.isAll() ? 101 : 103; |
| | | } else if (ioWorkType.equals(IoWorkType.CHECK_OUT)) { |
| | | ioType = 107; |
| | | } |
| | | assert ioType != null; |
| | | |
| | | Integer outSta = staNo.getDevNo(); |
| | | |
| | | // 获取路径 |
| | | StaDesc staDesc = staDescService.queryCrnStn(ioType, locMast.getCrnNo(), outSta); |
| | | |
| | | // 生成工作号 |
| | | int workNo = commonService.getWorkNo(WorkNoType.getWorkNoType(ioType)); |
| | | String pick = ioType == 101 ? "N":"Y"; |
| | | |
| | | } else { |
| | | priority = basePriority - index * 1.0; |
| | | } |
| | | OutLocDto dto; |
| | | Integer outSta = staNo.getDevNo(); |
| | | StaDesc staDesc = null; |
| | | if(ioType != 11){ |
| | | staDesc = staDescService.queryCrnStn(ioType, locMast.getCrnNo(), outSta); |
| | | } |
| | | LocMast locMastNew = null; |
| | | WrkMast wrkMast = new WrkMast(); |
| | | String locSts; |
| | | int workNo = commonService.getWorkNo(WorkNoType.getWorkNoType(ioType)); |
| | | String pick = (ioType == 101) ? "N" : "Y"; |
| | | if((ioType == 101 || ioType == 103 || ioType == 107) && staDesc != null) { |
| | | // 找到对应的 OutLocDto |
| | | dto = dtos.stream() |
| | | .filter(d -> d.getLocNo().equals(locNo)) |
| | | .findFirst() |
| | | .orElseThrow(() -> new CoolException("找不到对应的出库明细:" + locNo)); |
| | | // 生成工作档 |
| | | WrkMast wrkMast = new WrkMast(); |
| | | wrkMast.setWrkNo(workNo); |
| | | wrkMast.setIoTime(now); |
| | | wrkMast.setWrkSts(0L); |
| | |
| | | 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.setAppeTime(now); |
| | | wrkMast.setModiUser(userId); |
| | | wrkMast.setModiTime(now); |
| | | if (!wrkMastService.insert(wrkMast)) { |
| | | throw new CoolException("保存工作档失败,出库库位号:" + dto.getLocNo()); |
| | | } |
| | | |
| | | // 生成工作档明细 |
| | | locSts = ioType != 101? "R" : "P"; |
| | | // 生成工作档明细(保留原逻辑) |
| | | for (LocDetlDto detlDto : dto.getLocDetlDtos()) { |
| | | if (detlDto.getCount() == null || detlDto.getCount() <= 0.0D) { |
| | | continue; |
| | |
| | | throw new CoolException("保存工作档明细失败"); |
| | | } |
| | | } |
| | | }else{ |
| | | List<LocDetl> locDetls = locDetlService.selectList(new EntityWrapper<LocDetl>().eq("loc_no",locNo)); |
| | | FindLocNoAttributeVo findLocNoAttributeVo = new FindLocNoAttributeVo(); |
| | | findLocNoAttributeVo.setMatnr(locDetls.get(0).getMatnr()); |
| | | findLocNoAttributeVo.setBatch(locDetls.get(0).getBatch()); |
| | | LocTypeDto locTypeDto = new LocTypeDto(); |
| | | locTypeDto.setLocType1(locMast.getLocType1()); |
| | | locMastNew = commonService.searchMaxPallet(findLocNoAttributeVo,locTypeDto); |
| | | // 生成工作档 |
| | | wrkMast.setWrkNo(workNo); |
| | | wrkMast.setIoTime(now); |
| | | wrkMast.setWrkSts(0L); |
| | | wrkMast.setIoType(ioType); |
| | | wrkMast.setIoPri(priority); |
| | | wrkMast.setCrnNo(locMast.getCrnNo()); |
| | | wrkMast.setSourceStaNo(null); |
| | | wrkMast.setStaNo(null); |
| | | wrkMast.setSourceLocNo(locNo); |
| | | wrkMast.setLocNo(locMastNew.getLocNo()); |
| | | wrkMast.setFullPlt("Y"); |
| | | wrkMast.setPicking(pick); |
| | | wrkMast.setExitMk("N"); |
| | | wrkMast.setEmptyMk("N"); |
| | | wrkMast.setLinkMis("N"); |
| | | wrkMast.setBarcode(locMast.getBarcode()); |
| | | wrkMast.setAppeUser(userId); |
| | | wrkMast.setAppeTime(now); |
| | | wrkMast.setModiUser(userId); |
| | | wrkMast.setModiTime(now); |
| | | locSts = "R"; |
| | | // 生成工作档明细(保留原逻辑) |
| | | if(locDetls != null && locDetls.size() > 0) { |
| | | for (LocDetl locDetl : locDetls) { |
| | | WrkDetl wrkDetl = new WrkDetl(); |
| | | wrkDetl.sync(locDetl); |
| | | wrkDetl.setWrkNo(workNo); |
| | | wrkDetl.setIoTime(now); |
| | | |
| | | // 修改库位状态 |
| | | locMast = locMastService.selectById(dto.getLocNo()); |
| | | 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()); |
| | | wrkDetl.setAppeTime(now); |
| | | wrkDetl.setAppeUser(userId); |
| | | wrkDetl.setModiTime(now); |
| | | wrkDetl.setModiUser(userId); |
| | | if (!wrkDetlService.insert(wrkDetl)) { |
| | | throw new CoolException("保存工作档明细失败"); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | if (!wrkMastService.insert(wrkMast)) { |
| | | throw new CoolException("保存工作档失败,库位:" + locNo); |
| | | } |
| | | |
| | | if(locMastNew != null){ |
| | | if ("O".equals(locMastNew.getLocSts())) { |
| | | locMastNew.setLocSts("S"); |
| | | locMastNew.setModiUser(userId); |
| | | locMastNew.setModiTime(now); |
| | | if (!locMastService.updateById(locMastNew)) { |
| | | throw new CoolException("更新库位状态失败,库位:" + locNo); |
| | | } |
| | | } else { |
| | | throw new CoolException(dto.getLocNo() + "库位不是在库状态"); |
| | | throw new CoolException("库位状态异常,非空板状态:" + locNo); |
| | | } |
| | | } |
| | | // 更新库位状态 |
| | | locMast = locMastService.selectById(locNo); |
| | | if ("F".equals(locMast.getLocSts())) { |
| | | locMast.setLocSts(locSts); |
| | | locMast.setModiUser(userId); |
| | | locMast.setModiTime(now); |
| | | if (!locMastService.updateById(locMast)) { |
| | | throw new CoolException("预约库位状态失败,库位号:" + locNo); |
| | | } |
| | | } else { |
| | | 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) + |
| | | // " 两侧前方都有空板/故障,无法出库(正序或倒序方向都堵塞)" |
| | | // ); |
| | | // } |
| | | if (!leftClear ) { |
| | | 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)); |
| | | } |
| | | |
| | | /** |
| | | * 当选中段两侧前方都堵塞时,判断补哪一侧需要的空板最少,并把那些库位加入补充列表 |
| | | */ |
| | | private void supplementBothSidesBlocked( |
| | | List<LocMast> normalMasts, |
| | | List<LocGroupOrder> locGroupAscOrder, |
| | | List<LocMast> supplementMasts, |
| | | AtomicReference<Boolean> isLeftSideSupplement) { |
| | | |
| | | // 假设所有 normalMasts 在同一个分组(如果多分组,可循环处理每个分组) |
| | | LocGroupOrder group = locGroupAscOrder.stream() |
| | | .filter(g -> g.getRowList().contains(normalMasts.get(0).getRow1())) |
| | | .findFirst() |
| | | .orElseThrow(() -> new CoolException("分组异常")); |
| | | |
| | | List<Integer> fullRows = group.getRowList(); |
| | | |
| | | // 取选中段的 min/max row |
| | | Set<Integer> selectedRowSet = normalMasts.stream() |
| | | .map(LocMast::getRow1) |
| | | .collect(Collectors.toSet()); |
| | | |
| | | int minRow = Collections.min(selectedRowSet); |
| | | int maxRow = Collections.max(selectedRowSet); |
| | | |
| | | int minIndex = fullRows.indexOf(minRow); |
| | | int maxIndex = fullRows.indexOf(maxRow); |
| | | |
| | | // 假设所有库位在同 bay 和 lev |
| | | Integer bay1 = normalMasts.get(0).getBay1(); |
| | | Integer lev1 = normalMasts.get(0).getLev1(); |
| | | |
| | | // 计算左侧前方需要补充的 D 状态库位数量(从 0 到 minIndex-1 的 D 状态库位) |
| | | int leftSupplementCount = 0; |
| | | List<LocMast> leftSupplementLocs = new ArrayList<>(); |
| | | for (int i = 0; i < minIndex; i++) { |
| | | LocMast loc = getLocMastByRow(fullRows.get(i), bay1, lev1); |
| | | if (loc != null && ("D".equals(loc.getLocSts()) || "F".equals(loc.getLocSts()))) { |
| | | leftSupplementCount++; |
| | | leftSupplementLocs.add(loc); |
| | | } |
| | | } |
| | | |
| | | // // 计算右侧前方需要补充的 D 状态库位数量(从 maxIndex+1 到 end 的 D 状态库位) |
| | | // int rightSupplementCount = 0; |
| | | // List<LocMast> rightSupplementLocs = new ArrayList<>(); |
| | | // for (int i = maxIndex + 1; i < fullRows.size(); i++) { |
| | | // LocMast loc = getLocMastByRow(fullRows.get(i), bay1, lev1); |
| | | // if (loc != null && "D".equals(loc.getLocSts())) { |
| | | // rightSupplementCount++; |
| | | // rightSupplementLocs.add(loc); |
| | | // } |
| | | // } |
| | | |
| | | // 选择需要补充最少的一侧(如果相等,优先左侧) |
| | | List<LocMast> chosenSupplementLocs; |
| | | boolean isLeft = false; |
| | | if (true) { |
| | | chosenSupplementLocs = leftSupplementLocs; |
| | | isLeft = true; |
| | | log.info("选择补充左侧前方,共 {} 个库位", leftSupplementCount); |
| | | } |
| | | // else { |
| | | // chosenSupplementLocs = rightSupplementLocs; |
| | | // isLeft = false; |
| | | // log.info("选择补充右侧前方,共 {} 个库位", rightSupplementCount); |
| | | // } |
| | | // 记录选择的侧 |
| | | isLeftSideSupplement.set(isLeft); |
| | | // 添加到 supplementMasts(避免重复添加) |
| | | for (LocMast supp : chosenSupplementLocs) { |
| | | if (!supplementMasts.contains(supp) && !normalMasts.contains(supp)) { |
| | | supplementMasts.add(supp); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @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.getLocGroupAscOrder(); |
| | | |
| | | // 1. 查询所有选中的库位信息 |
| | | List<LocMast> selectedMasts = locMastService.selectList( |
| | | new EntityWrapper<LocMast>().in("loc_no", param.getLocNos()) |
| | | ); |
| | | |
| | | if (selectedMasts.size() != param.getLocNos().size()) { |
| | | throw new CoolException("部分选中库位不存在或数据异常"); |
| | | } |
| | | |
| | | // 2. 区分正常分组内的库位 和 需要补充的库位(规则外的) |
| | | List<LocMast> normalMasts = new ArrayList<>(); |
| | | List<LocMast> supplementMasts = new ArrayList<>(); |
| | | for (LocMast lm : selectedMasts) { |
| | | boolean inAnyGroup = locGroupAscOrder.stream() |
| | | .anyMatch(g -> g.getRowList().contains(lm.getRow1())); |
| | | if (inAnyGroup) { |
| | | normalMasts.add(lm); |
| | | } else { |
| | | supplementMasts.add(lm); |
| | | } |
| | | } |
| | | // 新增:记录是否因为左侧前方被补充 |
| | | AtomicReference<Boolean> isLeftSideSupplement = new AtomicReference<>(false); |
| | | // 3. 对正常分组内的库位进行检查(放宽两侧堵塞规则) |
| | | if (!normalMasts.isEmpty()) { |
| | | List<String> normalLocNos = normalMasts.stream() |
| | | .map(LocMast::getLocNo) |
| | | .collect(Collectors.toList()); |
| | | |
| | | LockingCheckResultParam checkResult = checkEmptyPlateBlocking( |
| | | normalLocNos, |
| | | locGroupAscOrder |
| | | ); |
| | | |
| | | if (!checkResult.isSuccess()) { |
| | | String errMsg = checkResult.getErrorMessage(); |
| | | if (errMsg.contains("两侧前方都有空板/故障")) { |
| | | // 两侧都堵 → 进入补齐逻辑 |
| | | supplementBothSidesBlocked(normalMasts, locGroupAscOrder, supplementMasts, isLeftSideSupplement); |
| | | } else { |
| | | // 其他错误(如不连续、非D状态)抛出 |
| | | throw new CoolException(errMsg); |
| | | } |
| | | } |
| | | // 如果有一侧清空,则正常继续 |
| | | } |
| | | |
| | | // 4. 合并所有库位(正常 + 补充的,包括规则外的和前方补的) |
| | | List<LocMast> allMasts = new ArrayList<>(); |
| | | allMasts.addAll(normalMasts); |
| | | allMasts.addAll(supplementMasts); |
| | | |
| | | if (allMasts.isEmpty()) { |
| | | throw new CoolException("没有有效的空板库位可出库"); |
| | | } |
| | | |
| | | // 5. 统一按 row → bay → lev 排序(从小到大) |
| | | List<LocMast> sortedAll = allMasts.stream() |
| | | .sorted(Comparator.comparing(LocMast::getRow1) |
| | | .thenComparing(LocMast::getBay1) |
| | | .thenComparing(LocMast::getLev1)) |
| | | .collect(Collectors.toList()); |
| | | |
| | | Date now = new Date(); |
| | | |
| | | // 6. 按排序后的顺序生成出库任务(从后往前生成) |
| | | for (int index = 0; index <sortedAll.size() ; index ++) { |
| | | LocMast locMast = sortedAll.get(index); |
| | | String locNo = locMast.getLocNo(); |
| | | |
| | | // 判断是否为补充库位(规则外的 或 前方补的) |
| | | boolean isSupplement = supplementMasts.contains(locMast); |
| | | |
| | | int ioType = isSupplement ? 11 : 110; |
| | | |
| | | // 获取工作号 |
| | | 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) |
| | | .eq("stn_no", param.getOutSite()) |
| | | .eq("crn_no", locMast.getCrnNo()); |
| | | StaDesc staDesc = staDescService.selectOne(wrapper); |
| | | Integer sourceStaNo = staDesc.getCrnStn(); |
| | | Integer sourceStaNo = staDesc != null ? staDesc.getCrnStn() : null; |
| | | |
| | | if (Cools.isEmpty(sourceStaNo)) { |
| | | throw new CoolException("检索源站失败"); |
| | | throw new CoolException("检索源站失败,库位:" + locNo); |
| | | } |
| | | Date now = new Date(); |
| | | // 保存工作档 |
| | | |
| | | // 计算优先级(示例:补充的优先级稍低) |
| | | double BASE_PRI = 200.0; |
| | | double ioPri; |
| | | 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.setCrnNo(locMast.getCrnNo()); |
| | | wrkMast.setSourceLocNo(locNo); // 源库位 |
| | | wrkMast.setFullPlt("N"); // 满板:Y |
| | | 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("保存工作档失败"); |
| | | LocMast locMastNew = null; |
| | | if(isSupplement){ |
| | | // 补充的库位:根据是左侧还是右侧补充,决定优先级方向 |
| | | if (Boolean.TRUE.equals(isLeftSideSupplement.get())) { |
| | | // 左侧补充 → 优先级较低(晚出) |
| | | ioPri = BASE_PRI + index * 1.0; |
| | | } else { |
| | | // 右侧补充 → 优先级较高(早出) |
| | | ioPri = BASE_PRI - index * 1.0; |
| | | } |
| | | locMastNew = commonService.searchEmptyPallet(null); |
| | | wrkMast.setWrkNo(workNo); |
| | | wrkMast.setIoTime(now); |
| | | wrkMast.setWrkSts(0L); |
| | | wrkMast.setIoType(ioType); |
| | | wrkMast.setIoPri(ioPri); |
| | | wrkMast.setSourceStaNo(null); |
| | | wrkMast.setLocNo(locMastNew.getLocNo()); |
| | | wrkMast.setStaNo(null); |
| | | wrkMast.setCrnNo(locMast.getCrnNo()); |
| | | 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); |
| | | }else{ |
| | | ioPri = BASE_PRI + index * 1.0; |
| | | wrkMast.setWrkNo(workNo); |
| | | wrkMast.setIoTime(now); |
| | | wrkMast.setWrkSts(0L); |
| | | wrkMast.setIoType(ioType); |
| | | wrkMast.setIoPri(ioPri); |
| | | wrkMast.setSourceStaNo(sourceStaNo); |
| | | wrkMast.setStaNo(param.getOutSite()); |
| | | wrkMast.setCrnNo(locMast.getCrnNo()); |
| | | 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); |
| | | } |
| | | // 更新库位状态 D.空板 -> R.出库预约 |
| | | if (locMast.getLocSts().equals("D")){ |
| | | |
| | | if (!wrkMastService.insert(wrkMast)) { |
| | | throw new CoolException("保存工作档失败,库位:" + locNo); |
| | | } |
| | | |
| | | if(locMastNew != null){ |
| | | if ("O".equals(locMastNew.getLocSts())) { |
| | | locMastNew.setLocSts("S"); |
| | | locMastNew.setModiUser(userId); |
| | | locMastNew.setModiTime(now); |
| | | if (!locMastService.updateById(locMastNew)) { |
| | | throw new CoolException("更新库位状态失败,库位:" + locNo); |
| | | } |
| | | } else { |
| | | throw new CoolException("库位状态异常,非空板状态:" + locNo); |
| | | } |
| | | } |
| | | |
| | | // 更新库位状态 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); |
| | | } |
| | | } |
| | | } |
| | |
| | | } else if (wrkMast.getIoType() > 100 && wrkMast.getWrkSts() != 14) { |
| | | locNo = wrkMast.getSourceLocNo(); |
| | | // 出库 ===>> F.在库 |
| | | if (wrkMast.getIoType() == 101 || wrkMast.getIoType() == 103) { |
| | | if (wrkMast.getIoType() == 101 || wrkMast.getIoType() == 103 || wrkMast.getIoType() == 107) { |
| | | locSts = "F"; |
| | | // 空板出库 ===>> D.空桶/空栈板 |
| | | } else if (wrkMast.getIoType() == 110) { |