自动化立体仓库 - WMS系统
chen.llin
3 天以前 9611dc686299be640ce5e5f5990c747765161ec7
src/main/java/com/zy/asrs/task/handler/WorkMastHandler.java
@@ -629,9 +629,40 @@
    public ReturnT<String> agvDoOut(Task task) {
        if (task.getIoType().equals(101)) {
            Date now = new Date();
            // 检查sourceLocNo是否为空
            if (task.getSourceLocNo() == null || task.getSourceLocNo().isEmpty()) {
//                log.warn("任务ID:{}的sourceLocNo为空,跳过库位操作(可能是从站点入库的任务)", task.getId());
                task.setWrkSts(15L);
                if (!taskService.updateById(task)) {
                    throw new CoolException("任务状态修改失败!!");
                }
                return SUCCESS;
            }
            // 查询库位信息
            LocCache locMast = locCacheService.selectOne(new EntityWrapper<LocCache>().eq("loc_no", task.getSourceLocNo()));
            if (Objects.isNull(locMast)) {
                throw new RuntimeException("数据错误:库位信息不能为空!!");
                // 如果查询不到库位,可能是sourceLocNo是站点号而不是库位号
                // 检查BasDevp表中是否存在该站点
                try {
                    Integer siteNo = Integer.parseInt(task.getSourceLocNo());
                    List<BasDevp> basDevpList = basDevpMapper.selectList(new EntityWrapper<BasDevp>().eq("dev_no", siteNo));
                    if (basDevpList != null && !basDevpList.isEmpty()) {
                        // sourceLocNo是站点号,这是从站点入库的任务,不需要处理库位
//                        log.info("任务ID:{}的sourceLocNo是站点号:{},跳过库位操作(从站点入库任务)", task.getId(), task.getSourceLocNo());
                        task.setWrkSts(15L);
                        if (!taskService.updateById(task)) {
                            throw new CoolException("任务状态修改失败!!");
                        }
                        return SUCCESS;
                    }
                } catch (NumberFormatException e) {
                    // sourceLocNo不是数字,可能是库位号格式错误
                    log.warn("任务ID:{}的sourceLocNo:{}不是有效的数字格式", task.getId(), task.getSourceLocNo());
                }
                // 既不是库位也不是站点,抛出异常
                throw new RuntimeException("数据错误:库位信息不能为空!!任务ID:" + task.getId() + ",sourceLocNo:" + task.getSourceLocNo());
            }
            List<TaskDetl> wrkDetls101 = taskDetlService.selectList(new EntityWrapper<TaskDetl>().eq("wrk_no", task.getWrkNo()));
            if (wrkDetls101.isEmpty()) {
@@ -711,6 +742,7 @@
                .eq("barcode", completedTask.getBarcode())
                .in("io_type", 110, 101) // 空板出库或全板出库
                .eq("wrk_sts", 7) // 待呼叫AGV状态
                .andNew("(is_deleted = 0)")
        );
        if (!existingTasks.isEmpty()) {
            log.info("任务ID:{}的托盘码:{}已存在空托/满托出库任务,跳过生成", completedTask.getId(), completedTask.getBarcode());
@@ -781,15 +813,9 @@
        }
        
        // 分配缓存库位:只查找WA开头的库位(CA开头只做入库,WA开头才会被出库分配缓存区)
        // 使用新的分配逻辑:按列优先级(第三列→第二列→第一列)分配
        String cacheAreaPrefix = agvProperties.getLocationPrefix().getCacheArea();
        LocCache cacheLoc = locCacheService.selectOne(new EntityWrapper<LocCache>()
                .eq("whs_type", targetWhsType) // 根据出库站点判断的whs_type
                .like("loc_no", cacheAreaPrefix + "%") // 只查找WA开头的库位(从配置读取)
                .eq("frozen", 0)
                .eq("loc_sts", LocStsType.LOC_STS_TYPE_O.type) // O.闲置
                .ne("full_plt", isEmptyPallet ? "Y" : "N") // 空托不选满板库位,满托不选空板库位
                .orderAsc(Arrays.asList("row1", "bay1", "lev1"))
                .last("OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY"));
        LocCache cacheLoc = allocateCacheLocationByPriority(targetWhsType, cacheAreaPrefix, isEmptyPallet);
        
        if (cacheLoc == null) {
            log.warn("{}侧没有可用的{}缓存库位,不生成{}AGV任务,任务ID:{}", 
@@ -837,6 +863,7 @@
                .eq("task_type", "agv")
                .eq("wrk_sts", 8L) // 只检查正在搬运状态的任务
                .in("io_type", 101, 110) // 出库到缓存区的任务类型
                .andNew("(is_deleted = 0)")
        );
        
        // 如果有正在搬运的任务,且工作档未完成,则不分配缓存库位
@@ -902,6 +929,167 @@
        
        log.info("成功生成{}任务,任务ID:{},工作号:{},源站点:{},目标站点:{},缓存库位:{}", 
                isEmptyPallet ? "空托出库" : "满托出库", cacheTask.getId(), workNo, outboundStaNo, cacheStaNo, cacheLoc.getLocNo());
    }
    /**
     * 按优先级分配缓存库位
     * 优先级规则:
     * 1. 优先分配第三列(bay1=3),且该排的1、2、3列都是空的
     * 2. 如果所有第三列都有货,则分配第二列(bay1=2),且该排的1、2列都是空的
     * 3. 如果所有排的第二第三列都满了,则分配第一列(bay1=1)
     * 4. 如果所有第一列都满了,再检查第二列和第三列
     * 5. 层(lev1)从第一层开始
     *
     * @param whsType 库区类型
     * @param cacheAreaPrefix 缓存区库位前缀(如"WA")
     * @param isEmptyPallet 是否空托
     * @return 分配的缓存库位,如果无法分配则返回null
     */
    private LocCache allocateCacheLocationByPriority(Long whsType, String cacheAreaPrefix, boolean isEmptyPallet) {
        // 查询所有符合条件的空库位
        List<LocCache> allLocations = locCacheService.selectList(new EntityWrapper<LocCache>()
                .eq("whs_type", whsType)
                .like("loc_no", cacheAreaPrefix + "%")
                .eq("frozen", 0)
                .eq("loc_sts", LocStsType.LOC_STS_TYPE_O.type) // O.闲置
                .ne("full_plt", isEmptyPallet ? "Y" : "N") // 空托不选满板库位,满托不选空板库位
        );
        if (allLocations == null || allLocations.isEmpty()) {
            return null;
        }
        // 按row1分组
        Map<Integer, List<LocCache>> locationsByRow = allLocations.stream()
                .filter(loc -> loc.getRow1() != null)
                .collect(Collectors.groupingBy(LocCache::getRow1));
        if (locationsByRow.isEmpty()) {
            return null;
        }
        // 对每个排,检查1、2、3列的状态
        // 列状态:true表示该列有空库位,false表示该列已满
        Map<Integer, Map<Integer, Boolean>> rowColumnStatus = new HashMap<>();
        for (Map.Entry<Integer, List<LocCache>> entry : locationsByRow.entrySet()) {
            Integer row = entry.getKey();
            List<LocCache> rowLocs = entry.getValue();
            Map<Integer, Boolean> columnStatus = new HashMap<>();
            // 检查第1、2、3列是否有空库位
            for (int bay = 1; bay <= 3; bay++) {
                final int bayFinal = bay;  // 创建final副本供lambda使用
                boolean hasEmpty = rowLocs.stream()
                        .anyMatch(loc -> loc.getBay1() != null && loc.getBay1() == bayFinal);
                columnStatus.put(bay, hasEmpty);
            }
            rowColumnStatus.put(row, columnStatus);
        }
        // 优先级1:分配第三列(bay1=3),且该排的1、2、3列都是空的
        for (Map.Entry<Integer, List<LocCache>> entry : locationsByRow.entrySet()) {
            Integer row = entry.getKey();
            Map<Integer, Boolean> columnStatus = rowColumnStatus.get(row);
            // 检查该排的1、2、3列是否都是空的
            if (Boolean.TRUE.equals(columnStatus.get(1)) &&
                Boolean.TRUE.equals(columnStatus.get(2)) &&
                Boolean.TRUE.equals(columnStatus.get(3))) {
                // 分配该排的第三列,从第一层开始
                List<LocCache> bay3Locs = entry.getValue().stream()
                        .filter(loc -> loc.getBay1() != null && loc.getBay1() == 3)
                        .sorted(Comparator.comparing(loc -> loc.getLev1() != null ? loc.getLev1() : 0))
                        .collect(Collectors.toList());
                if (!bay3Locs.isEmpty()) {
                    log.debug("优先级1:分配排{}的第三列,库位:{}", row, bay3Locs.get(0).getLocNo());
                    return bay3Locs.get(0);
                }
            }
        }
        // 优先级2:分配第二列(bay1=2),且该排的1、2列都是空的(第三列可能已满)
        for (Map.Entry<Integer, List<LocCache>> entry : locationsByRow.entrySet()) {
            Integer row = entry.getKey();
            Map<Integer, Boolean> columnStatus = rowColumnStatus.get(row);
            // 检查该排的1、2列是否都是空的
            if (Boolean.TRUE.equals(columnStatus.get(1)) &&
                Boolean.TRUE.equals(columnStatus.get(2))) {
                // 分配该排的第二列,从第一层开始
                List<LocCache> bay2Locs = entry.getValue().stream()
                        .filter(loc -> loc.getBay1() != null && loc.getBay1() == 2)
                        .sorted(Comparator.comparing(loc -> loc.getLev1() != null ? loc.getLev1() : 0))
                        .collect(Collectors.toList());
                if (!bay2Locs.isEmpty()) {
                    log.debug("优先级2:分配排{}的第二列,库位:{}", row, bay2Locs.get(0).getLocNo());
                    return bay2Locs.get(0);
                }
            }
        }
        // 优先级3:分配第一列(bay1=1),所有排的第二第三列都满了
        for (Map.Entry<Integer, List<LocCache>> entry : locationsByRow.entrySet()) {
            Integer row = entry.getKey();
            Map<Integer, Boolean> columnStatus = rowColumnStatus.get(row);
            // 检查该排的第一列是否有空库位
            if (Boolean.TRUE.equals(columnStatus.get(1))) {
                // 分配该排的第一列,从第一层开始
                List<LocCache> bay1Locs = entry.getValue().stream()
                        .filter(loc -> loc.getBay1() != null && loc.getBay1() == 1)
                        .sorted(Comparator.comparing(loc -> loc.getLev1() != null ? loc.getLev1() : 0))
                        .collect(Collectors.toList());
                if (!bay1Locs.isEmpty()) {
                    log.debug("优先级3:分配排{}的第一列,库位:{}", row, bay1Locs.get(0).getLocNo());
                    return bay1Locs.get(0);
                }
            }
        }
        // 优先级4:如果所有第一列都满了,再检查第二列和第三列(不要求该排的1、2列都是空的)
        // 先检查第二列
        for (Map.Entry<Integer, List<LocCache>> entry : locationsByRow.entrySet()) {
            Integer row = entry.getKey();
            Map<Integer, Boolean> columnStatus = rowColumnStatus.get(row);
            // 检查该排的第二列是否有空库位
            if (Boolean.TRUE.equals(columnStatus.get(2))) {
                List<LocCache> bay2Locs = entry.getValue().stream()
                        .filter(loc -> loc.getBay1() != null && loc.getBay1() == 2)
                        .sorted(Comparator.comparing(loc -> loc.getLev1() != null ? loc.getLev1() : 0))
                        .collect(Collectors.toList());
                if (!bay2Locs.isEmpty()) {
                    log.debug("优先级4:分配排{}的第二列,库位:{}", row, bay2Locs.get(0).getLocNo());
                    return bay2Locs.get(0);
                }
            }
        }
        // 优先级5:最后检查第三列(不要求该排的1、2、3列都是空的)
        for (Map.Entry<Integer, List<LocCache>> entry : locationsByRow.entrySet()) {
            Integer row = entry.getKey();
            Map<Integer, Boolean> columnStatus = rowColumnStatus.get(row);
            // 检查该排的第三列是否有空库位
            if (Boolean.TRUE.equals(columnStatus.get(3))) {
                List<LocCache> bay3Locs = entry.getValue().stream()
                        .filter(loc -> loc.getBay1() != null && loc.getBay1() == 3)
                        .sorted(Comparator.comparing(loc -> loc.getLev1() != null ? loc.getLev1() : 0))
                        .collect(Collectors.toList());
                if (!bay3Locs.isEmpty()) {
                    log.debug("优先级5:分配排{}的第三列,库位:{}", row, bay3Locs.get(0).getLocNo());
                    return bay3Locs.get(0);
                }
            }
        }
        // 如果所有列都满了,返回null
        return null;
    }
    
    /**
@@ -990,6 +1178,7 @@
                    .eq("task_type", "agv")
                    .eq("wrk_sts", 8L) // 只检查正在搬运状态的任务
                    .in("io_type", checkIoTypes)
                    .andNew("(is_deleted = 0)")
            );
            
            if (!transportingTasks.isEmpty()) {
@@ -1006,7 +1195,7 @@
        
        // 如果所有站点都在搬运,则不分配站点
        if (selectedSite == null) {
            log.warn("所有缓存区站点都有正在搬运的出库任务,暂不分配站点,等待空闲");
//            log.warn("所有缓存区站点都有正在搬运的出库任务,暂不分配站点,等待空闲");
            return null;
        }
        
@@ -1027,9 +1216,42 @@
        if (ioType == 1 || ioType == 10 || ioType == 53 || ioType == 57) {
            // ioType == 1 需要处理组托数据
            if (ioType == 1) {
                // 检查locNo是否为空
                if (wrkMast.getLocNo() == null || wrkMast.getLocNo().isEmpty()) {
                    log.warn("任务ID:{}的locNo为空,跳过库位操作(可能是从站点入库的任务)", wrkMast.getId());
                    // 更新任务状态为5(库存更新完成)
                    wrkMast.setWrkSts(5L);
                    wrkMast.setModiTime(new Date());
                    if (!taskService.updateById(wrkMast)) {
                        throw new CoolException("任务状态修改失败!!");
                    }
                    return SUCCESS;
                }
                LocCache locCache = locCacheService.selectOne(new EntityWrapper<LocCache>().eq("loc_no", wrkMast.getLocNo()));
                if (Objects.isNull(locCache)) {
                    throw new CoolException("数据错误,库位不存在!!");
                    // 如果查询不到库位,可能是locNo是站点号而不是库位号
                    // 检查BasDevp表中是否存在该站点
                    try {
                        Integer siteNo = Integer.parseInt(wrkMast.getLocNo());
                        List<BasDevp> basDevpList = basDevpMapper.selectList(new EntityWrapper<BasDevp>().eq("dev_no", siteNo));
                        if (basDevpList != null && !basDevpList.isEmpty()) {
                            // locNo是站点号,这是从站点入库的任务,不需要处理库位明细
                            log.info("任务ID:{}的locNo是站点号:{},跳过库位操作(从站点入库任务)", wrkMast.getId(), wrkMast.getLocNo());
                            // 更新任务状态为5(库存更新完成)
                            wrkMast.setWrkSts(5L);
                            wrkMast.setModiTime(new Date());
                            if (!taskService.updateById(wrkMast)) {
                                throw new CoolException("任务状态修改失败!!");
                            }
                            return SUCCESS;
                        }
                    } catch (NumberFormatException e) {
                        // locNo不是数字,可能是库位号格式错误
                        log.warn("任务ID:{}的locNo:{}不是有效的数字格式", wrkMast.getId(), wrkMast.getLocNo());
                    }
                    // 既不是库位也不是站点,抛出异常
                    throw new CoolException("数据错误,库位不存在!!任务ID:" + wrkMast.getId() + ",locNo:" + wrkMast.getLocNo());
                }
                if (!locCache.getLocSts().equals(LocStsType.LOC_STS_TYPE_S.type)) {
                    throw new CoolException("当前库位状态为:" + LocStsType.LOC_STS_TYPE_S.type + "." + LocStsType.LOC_STS_TYPE_S.desc + ",不是出库预约状态");
@@ -1058,14 +1280,32 @@
                    }
                });
                locCache.setLocSts(LocStsType.LOC_STS_TYPE_F.type);
                // 根据fullPlt设置库位状态:满托设置为"F"(在库),空托设置为"D"(空桶/空栈板)
                boolean isFullPlt = wrkMast.getFullPlt() != null && wrkMast.getFullPlt().equals("Y");
                locCache.setLocSts(isFullPlt ? LocStsType.LOC_STS_TYPE_F.type : LocStsType.LOC_STS_TYPE_D.type);
                locCache.setFullPlt(isFullPlt ? "Y" : "N");
                locCache.setModiTime(new Date());
                locCache.setBarcode(wrkMast.getBarcode());
                locCache.setModiTime(new Date());
                locCache.setIoTime(new Date());
                if (!locCacheService.updateById(locCache)) {
                    throw new CoolException("库位状态修改失败!");
                }
            } else if (ioType == 10 || ioType == 53 || ioType == 57) {
                // 空托入库或其他入库类型,也需要更新缓存库位状态
                LocCache locCache = locCacheService.selectOne(new EntityWrapper<LocCache>().eq("loc_no", wrkMast.getLocNo()));
                if (locCache != null) {
                    // 根据fullPlt设置库位状态:满托设置为"F"(在库),空托设置为"D"(空桶/空栈板)
                    // ioType == 10 是空托入库,默认设置为"D"
                    boolean isFullPlt = (ioType != 10) && (wrkMast.getFullPlt() != null && wrkMast.getFullPlt().equals("Y"));
                    locCache.setLocSts(isFullPlt ? LocStsType.LOC_STS_TYPE_F.type : LocStsType.LOC_STS_TYPE_D.type);
                    locCache.setFullPlt(isFullPlt ? "Y" : "N");
                    locCache.setModiTime(new Date());
                    locCache.setBarcode(wrkMast.getBarcode());
                    locCache.setIoTime(new Date());
                    if (!locCacheService.updateById(locCache)) {
                        throw new CoolException("库位状态修改失败!");
                    }
                }
            }
            
            // 更新任务状态为5(库存更新完成)