| | |
| | | @RequestMapping("/menu/pda/auth") |
| | | @ManagerAuth |
| | | public R menuPda(){ |
| | | Long userId = getUserId(); |
| | | List<RolePermission> rolePermissions; |
| | | if (userId == 9527L) { |
| | | rolePermissions = rolePermissionService.selectList(new EntityWrapper<>()); |
| | | } else { |
| | | Long roleId = getUser().getRoleId(); |
| | | rolePermissions = rolePermissionService.selectList(new EntityWrapper<RolePermission>().eq("role_id", roleId)); |
| | | // 直接从 sys_permission 表读取所有菜单,不检查权限分配 |
| | | // 查询所有状态为1(正常)的权限 |
| | | List<Permission> permissions = permissionService.selectList( |
| | | new EntityWrapper<Permission>() |
| | | .eq("status", 1) // 只返回正常状态的权限 |
| | | ); |
| | | |
| | | // 检查是否有层级结构(父菜单:action为空字符串,子菜单:resource_id指向父菜单的permission.id) |
| | | // 查询所有父菜单(action为空字符串的权限) |
| | | List<Permission> parentMenus = permissions.stream() |
| | | .filter(p -> p.getAction() == null || p.getAction().isEmpty()) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 收集所有父菜单ID(用于过滤独立菜单) |
| | | java.util.Set<Long> parentMenuIds = parentMenus.stream() |
| | | .map(Permission::getId) |
| | | .collect(Collectors.toSet()); |
| | | |
| | | if (!parentMenus.isEmpty()) { |
| | | // 构建层级结构 |
| | | List<Map<String, Object>> result = new ArrayList<>(); |
| | | |
| | | for (Permission parentMenu : parentMenus) { |
| | | // 查找该父菜单下的子菜单 |
| | | // 方式1:resource_id指向父菜单的id |
| | | // 方式2:resource_id为0或null,但根据action路径匹配父菜单 |
| | | List<Permission> children = permissions.stream() |
| | | .filter(p -> { |
| | | // 排除父菜单本身 |
| | | if (p.getAction() == null || p.getAction().isEmpty()) { |
| | | return false; |
| | | } |
| | | // 方式1:resource_id指向父菜单的id |
| | | if (p.getResourceId() != null && p.getResourceId().equals(parentMenu.getId())) { |
| | | return true; |
| | | } |
| | | // 方式2:resource_id为0或null,根据action路径匹配 |
| | | if ((p.getResourceId() == null || p.getResourceId() == 0)) { |
| | | String action = p.getAction(); |
| | | String parentName = parentMenu.getName(); |
| | | // 根据父菜单名称匹配action路径 |
| | | if ("入库管理".equals(parentName)) { |
| | | return action != null && (action.startsWith("/pakin/") || action.startsWith("/order/")); |
| | | } else if ("AGV管理".equals(parentName)) { |
| | | return action != null && action.startsWith("/AGV/"); |
| | | } else if ("库存管理".equals(parentName)) { |
| | | return action != null && action.startsWith("/stock/"); |
| | | } |
| | | } |
| | | return false; |
| | | }) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 只有有子菜单的父菜单才返回 |
| | | if (!children.isEmpty()) { |
| | | Map<String, Object> parentMap = new HashMap<>(); |
| | | parentMap.put("id", parentMenu.getId()); |
| | | parentMap.put("name", parentMenu.getName()); |
| | | parentMap.put("action", parentMenu.getAction()); |
| | | parentMap.put("type", "parent"); // 标识为父菜单 |
| | | |
| | | // 构建子菜单列表 |
| | | List<Map<String, Object>> childrenList = new ArrayList<>(); |
| | | for (Permission child : children) { |
| | | Map<String, Object> childMap = new HashMap<>(); |
| | | childMap.put("id", child.getId()); |
| | | childMap.put("name", child.getName()); |
| | | childMap.put("action", child.getAction()); |
| | | childMap.put("type", "child"); // 标识为子菜单 |
| | | childrenList.add(childMap); |
| | | } |
| | | parentMap.put("children", childrenList); |
| | | result.add(parentMap); |
| | | } |
| | | } |
| | | |
| | | // 添加没有父菜单的独立权限(resource_id为NULL的权限) |
| | | List<Permission> standalonePermissions = permissions.stream() |
| | | .filter(p -> { |
| | | // 只返回有action的权限(排除父菜单) |
| | | if (p.getAction() == null || p.getAction().isEmpty()) { |
| | | return false; |
| | | } |
| | | // 如果resource_id为NULL,说明是独立菜单 |
| | | return p.getResourceId() == null; |
| | | }) |
| | | .collect(Collectors.toList()); |
| | | |
| | | for (Permission permission : standalonePermissions) { |
| | | Map<String, Object> item = new HashMap<>(); |
| | | item.put("id", permission.getId()); |
| | | item.put("name", permission.getName()); |
| | | item.put("action", permission.getAction()); |
| | | item.put("type", "standalone"); // 独立菜单 |
| | | result.add(item); |
| | | } |
| | | |
| | | return R.ok().add(result); |
| | | } |
| | | if (Cools.isEmpty(rolePermissions)) { |
| | | return R.ok(); |
| | | } |
| | | List<Long> collect = rolePermissions.stream().map(RolePermission::getPermissionId).distinct().collect(Collectors.toList()); |
| | | List<Permission> permissions = permissionService.selectBatchIds(collect); |
| | | |
| | | // 如果没有层级结构,返回原来的平铺结构(兼容旧逻辑) |
| | | return R.ok().add(permissions); |
| | | } |
| | | } |
| | |
| | | if (!Cools.isEmpty(orderByField)) { |
| | | wrapper.orderBy(humpToLine(orderByField), "asc".equals(orderByType)); |
| | | } |
| | | wrapper.eq("full_plt", "N"); |
| | | // 移除 full_plt = "N" 的限制,允许查询所有状态的库位(包括满托和空托) |
| | | // 如果需要在后端管理页面过滤,可以在前端进行过滤 |
| | | return R.ok(locCacheService.selectPage(new Page<>(curr, limit), wrapper)); |
| | | } |
| | | |
| | | private <T> void convert(Map<String, Object> map, EntityWrapper<T> wrapper) { |
| | | for (Map.Entry<String, Object> entry : map.entrySet()) { |
| | | String val = String.valueOf(entry.getValue()); |
| | | // 将驼峰命名转换为下划线命名(如 locNo -> loc_no) |
| | | String columnName = humpToLine(entry.getKey()); |
| | | if (val.contains(RANGE_TIME_LINK)) { |
| | | String[] dates = val.split(RANGE_TIME_LINK); |
| | | wrapper.ge(entry.getKey(), DateUtils.convert(dates[0])); |
| | | wrapper.le(entry.getKey(), DateUtils.convert(dates[1])); |
| | | wrapper.ge(columnName, DateUtils.convert(dates[0])); |
| | | wrapper.le(columnName, DateUtils.convert(dates[1])); |
| | | } else { |
| | | wrapper.like(entry.getKey(), val); |
| | | wrapper.like(columnName, val); |
| | | } |
| | | } |
| | | } |
| | |
| | | return locCacheService.initLocCache(param, getUserId()); |
| | | } |
| | | |
| | | /** |
| | | * 锁定/解锁缓存库位 |
| | | * @param locNo 库位号 |
| | | * @param lock 是否锁定,true-锁定,false-解锁 |
| | | * @param fullPlt 是否满托,true-满托(设置为F),false-空托(设置为D),解锁时可为空 |
| | | * @return 操作结果 |
| | | */ |
| | | @RequestMapping(value = "/locCache/lockOrUnlock/auth", method = RequestMethod.POST) |
| | | @ManagerAuth |
| | | public R lockOrUnlock(@RequestParam String locNo, @RequestParam Boolean lock, @RequestParam(required = false) Boolean fullPlt) { |
| | | if (Cools.isEmpty(locNo)) { |
| | | return R.error("库位号不能为空"); |
| | | } |
| | | if (lock == null) { |
| | | return R.error("锁定参数不能为空"); |
| | | } |
| | | return locCacheService.lockOrUnlockLocCache(locNo, lock, fullPlt, getUserId()); |
| | | } |
| | | |
| | | /** |
| | | * 锁定/解锁当前排的所有库位 |
| | | * @param locNo 库位号(用于获取排号) |
| | | * @param lock 是否锁定,true-锁定,false-解锁 |
| | | * @param fullPlt 是否满托,true-满托(设置为F),false-空托(设置为D),解锁时可为空 |
| | | * @return 操作结果 |
| | | */ |
| | | @RequestMapping(value = "/locCache/lockOrUnlockRow/auth", method = RequestMethod.POST) |
| | | @ManagerAuth |
| | | public R lockOrUnlockRow(@RequestParam String locNo, @RequestParam Boolean lock, @RequestParam(required = false) Boolean fullPlt) { |
| | | if (Cools.isEmpty(locNo)) { |
| | | return R.error("库位号不能为空"); |
| | | } |
| | | if (lock == null) { |
| | | return R.error("锁定参数不能为空"); |
| | | } |
| | | return locCacheService.lockOrUnlockRowLocCache(locNo, lock, fullPlt, getUserId()); |
| | | } |
| | | |
| | | /** |
| | | * 锁定/解锁当前列的所有库位 |
| | | * @param locNo 库位号(用于获取列号bay1) |
| | | * @param lock 是否锁定,true-锁定,false-解锁 |
| | | * @param fullPlt 是否满托,true-满托(设置为F),false-空托(设置为D),解锁时可为空 |
| | | * @return 操作结果 |
| | | */ |
| | | @RequestMapping(value = "/locCache/lockOrUnlockBay/auth", method = RequestMethod.POST) |
| | | @ManagerAuth |
| | | public R lockOrUnlockBay(@RequestParam String locNo, @RequestParam Boolean lock, @RequestParam(required = false) Boolean fullPlt) { |
| | | if (Cools.isEmpty(locNo)) { |
| | | return R.error("库位号不能为空"); |
| | | } |
| | | if (lock == null) { |
| | | return R.error("锁定参数不能为空"); |
| | | } |
| | | return locCacheService.lockOrUnlockBayLocCache(locNo, lock, fullPlt, getUserId()); |
| | | } |
| | | |
| | | /** |
| | | * 清空整排的所有库位(所有列) |
| | | * @param locNo 库位号(用于获取排号row1) |
| | | * @param lock 是否锁定,true-锁定,false-解锁(清空) |
| | | * @param fullPlt 是否满托,true-满托(设置为F),false-空托(设置为D),解锁时可为空 |
| | | * @return 操作结果 |
| | | */ |
| | | @RequestMapping(value = "/locCache/clearAllColumnsInRow/auth", method = RequestMethod.POST) |
| | | @ManagerAuth |
| | | public R clearAllColumnsInRow(@RequestParam String locNo, @RequestParam Boolean lock, @RequestParam(required = false) Boolean fullPlt) { |
| | | if (Cools.isEmpty(locNo)) { |
| | | return R.error("库位号不能为空"); |
| | | } |
| | | if (lock == null) { |
| | | return R.error("锁定参数不能为空"); |
| | | } |
| | | return locCacheService.clearAllColumnsInRow(locNo, lock, fullPlt, getUserId()); |
| | | } |
| | | |
| | | } |
| | |
| | | } |
| | | |
| | | public String getLocSts$() { |
| | | if (this.locSts.equals(LocStsType.LOC_STS_TYPE_F.type)) { |
| | | return LocStsType.LOC_STS_TYPE_F.desc; |
| | | } else if (locSts.equals(LocStsType.LOC_STS_TYPE_D.type)) { |
| | | if (this.locSts == null) { |
| | | return null; |
| | | } |
| | | if (this.locSts.equals(LocStsType.LOC_STS_TYPE_D.type)) { |
| | | return LocStsType.LOC_STS_TYPE_D.desc; |
| | | } else if (locSts.equals(LocStsType.LOC_STS_TYPE_O.type)) { |
| | | } else if (locSts.equals(LocStsType.LOC_STS_TYPE_F.type)) { |
| | | return LocStsType.LOC_STS_TYPE_F.desc; |
| | | } else if (locSts.equals(LocStsType.LOC_STS_TYPE_O.type)) { |
| | | return LocStsType.LOC_STS_TYPE_O.desc; |
| | | } else if (locSts.equals(LocStsType.LOC_STS_TYPE_P.type)) { |
| | | return LocStsType.LOC_STS_TYPE_P.desc; |
| | | } else if (locSts.equals(LocStsType.LOC_STS_TYPE_Q.type)) { |
| | | return LocStsType.LOC_STS_TYPE_Q.desc; |
| | | } else if (locSts.equals(LocStsType.LOC_STS_TYPE_R.type)) { |
| | | return LocStsType.LOC_STS_TYPE_R.desc; |
| | | } else if (locSts.equals(LocStsType.LOC_STS_TYPE_S.type)) { |
| | | return LocStsType.LOC_STS_TYPE_S.desc; |
| | | } else if (locSts.equals(LocStsType.LOC_STS_TYPE_X.type)) { |
| | | return LocStsType.LOC_STS_TYPE_X.desc; |
| | | } else if (locSts.equals(LocStsType.LOC_STS_TYPE_Y.type)) { |
| | | return LocStsType.LOC_STS_TYPE_Y.desc; |
| | | } else { |
| | | return null; |
| | | } |
| | |
| | | |
| | | public enum LocStsType { |
| | | |
| | | //空板 |
| | | LOC_STS_TYPE_D("D", "空板"), |
| | | //空桶/空栈板 |
| | | LOC_STS_TYPE_D("D", "空桶/空栈板"), |
| | | //在库 |
| | | LOC_STS_TYPE_F("F", "在库"), |
| | | //空库 |
| | | LOC_STS_TYPE_O("O", "空库"), |
| | | //禁用 |
| | | LOC_STS_TYPE_X("X", "禁用"), |
| | | //入库预约 |
| | | LOC_STS_TYPE_S("S", "入库预约"), |
| | | //空库位 |
| | | LOC_STS_TYPE_O("O", "空库位"), |
| | | //拣料/盘点/并板出库中 |
| | | LOC_STS_TYPE_P("P", "拣料/盘点/并板出库中"), |
| | | //拣料/盘点/并板再入库 |
| | | LOC_STS_TYPE_Q("Q", "拣料/盘点/并板再入库"), |
| | | //出库预约 |
| | | LOC_STS_TYPE_R("R", "出库预约"), |
| | | //入库预约 |
| | | LOC_STS_TYPE_S("S", "入库预约"), |
| | | //禁用 |
| | | LOC_STS_TYPE_X("X", "禁用"), |
| | | //被合并 |
| | | LOC_STS_TYPE_Y("Y", "被合并"), |
| | | ; |
| | | |
| | | public String type; |
| | |
| | | * @version 1.0 |
| | | */ |
| | | R initLocCache(LocMastInitParam param, Long userId); |
| | | |
| | | /** |
| | | * 锁定/解锁缓存库位 |
| | | * @param locNo 库位号 |
| | | * @param lock 是否锁定,true-锁定,false-解锁 |
| | | * @param fullPlt 是否满托,true-满托(设置为F),false-空托(设置为D),解锁时忽略此参数 |
| | | * @param userId 用户ID |
| | | * @return 操作结果 |
| | | */ |
| | | R lockOrUnlockLocCache(String locNo, Boolean lock, Boolean fullPlt, Long userId); |
| | | |
| | | /** |
| | | * 锁定/解锁当前排的所有库位 |
| | | * @param locNo 库位号(用于获取排号) |
| | | * @param lock 是否锁定,true-锁定,false-解锁 |
| | | * @param fullPlt 是否满托,true-满托(设置为F),false-空托(设置为D),解锁时忽略此参数 |
| | | * @param userId 用户ID |
| | | * @return 操作结果 |
| | | */ |
| | | R lockOrUnlockRowLocCache(String locNo, Boolean lock, Boolean fullPlt, Long userId); |
| | | |
| | | /** |
| | | * 锁定/解锁当前列的所有库位 |
| | | * @param locNo 库位号(用于获取列号bay1) |
| | | * @param lock 是否锁定,true-锁定,false-解锁 |
| | | * @param fullPlt 是否满托,true-满托(设置为F),false-空托(设置为D),解锁时忽略此参数 |
| | | * @param userId 用户ID |
| | | * @return 操作结果 |
| | | */ |
| | | R lockOrUnlockBayLocCache(String locNo, Boolean lock, Boolean fullPlt, Long userId); |
| | | |
| | | /** |
| | | * 清空整排的所有库位(所有列) |
| | | * @param locNo 库位号(用于获取排号row1) |
| | | * @param lock 是否锁定,true-锁定,false-解锁(清空) |
| | | * @param fullPlt 是否满托,true-满托(设置为F),false-空托(设置为D),解锁时忽略此参数 |
| | | * @param userId 用户ID |
| | | * @return 操作结果 |
| | | */ |
| | | R clearAllColumnsInRow(String locNo, Boolean lock, Boolean fullPlt, Long userId); |
| | | } |
| | |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | |
| | | return R.error("初始化失败===>" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 锁定/解锁缓存库位(复用AGV搬货后的库位有货逻辑) |
| | | * 锁定:满托设置为 "F"(在库),空托设置为 "D"(空桶/空栈板) |
| | | * 解锁:统一设置为 "O"(空库位) |
| | | * @param locNo 库位号 |
| | | * @param lock 是否锁定,true-锁定,false-解锁 |
| | | * @param fullPlt 是否满托,true-满托(设置为F),false-空托(设置为D),解锁时忽略此参数 |
| | | * @param userId 用户ID |
| | | * @return 操作结果 |
| | | */ |
| | | @Override |
| | | public R lockOrUnlockLocCache(String locNo, Boolean lock, Boolean fullPlt, Long userId) { |
| | | try { |
| | | if (Cools.isEmpty(locNo)) { |
| | | return R.error("库位号不能为空"); |
| | | } |
| | | |
| | | // 根据库位号查询库位 |
| | | EntityWrapper<LocCache> wrapper = new EntityWrapper<>(); |
| | | wrapper.eq("loc_no", locNo); |
| | | LocCache locCache = this.selectOne(wrapper); |
| | | |
| | | if (Cools.isEmpty(locCache)) { |
| | | return R.error("库位不存在:" + locNo); |
| | | } |
| | | |
| | | // 更新库位状态 |
| | | String locSts; |
| | | if (lock) { |
| | | // 锁定:满托设置为"F"(在库),空托设置为"D"(空桶/空栈板) |
| | | if (fullPlt == null) { |
| | | return R.error("锁定操作时,满托参数不能为空"); |
| | | } |
| | | locSts = fullPlt ? "F" : "D"; |
| | | locCache.setFullPlt(fullPlt ? "Y" : "N"); |
| | | } else { |
| | | // 解锁:统一设置为"O"(空库位) |
| | | locSts = "O"; |
| | | locCache.setFullPlt("N"); |
| | | } |
| | | |
| | | locCache.setLocSts(locSts); |
| | | locCache.setModiUser(userId); |
| | | locCache.setModiTime(new Date()); |
| | | |
| | | boolean success = this.updateById(locCache); |
| | | if (success) { |
| | | String action = lock ? "锁定" : "解锁"; |
| | | String detail = lock ? (fullPlt ? "满托" : "空托") : ""; |
| | | log.info("{}库位成功:{},库位状态:{},{},操作人:{}", action, locNo, locCache.getLocSts(), detail, userId); |
| | | return R.ok(action + "成功"); |
| | | } else { |
| | | return R.error("操作失败"); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("锁定/解锁库位失败", e); |
| | | return R.error("操作失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 锁定/解锁当前排的所有库位 |
| | | * @param locNo 库位号(用于获取排号) |
| | | * @param lock 是否锁定,true-锁定,false-解锁 |
| | | * @param userId 用户ID |
| | | * @return 操作结果 |
| | | */ |
| | | @Override |
| | | public R lockOrUnlockRowLocCache(String locNo, Boolean lock, Boolean fullPlt, Long userId) { |
| | | try { |
| | | if (Cools.isEmpty(locNo)) { |
| | | return R.error("库位号不能为空"); |
| | | } |
| | | |
| | | // 根据库位号查询库位,获取排号 |
| | | EntityWrapper<LocCache> wrapper = new EntityWrapper<>(); |
| | | wrapper.eq("loc_no", locNo); |
| | | LocCache locCache = this.selectOne(wrapper); |
| | | |
| | | if (Cools.isEmpty(locCache)) { |
| | | return R.error("库位不存在:" + locNo); |
| | | } |
| | | |
| | | Integer row = locCache.getRow1(); |
| | | if (Cools.isEmpty(row)) { |
| | | return R.error("库位排号为空:" + locNo); |
| | | } |
| | | |
| | | // 查询当前排的1、2、3列的所有库位(解锁整排时只清空1、2、3列) |
| | | EntityWrapper<LocCache> rowWrapper = new EntityWrapper<>(); |
| | | rowWrapper.eq("row1", row) |
| | | .in("bay1", Arrays.asList(1, 2, 3)); // 只查询1、2、3列 |
| | | // 如果库位有库区ID,也加上库区条件 |
| | | if (!Cools.isEmpty(locCache.getAreaId())) { |
| | | rowWrapper.eq("area_id", locCache.getAreaId()); |
| | | } |
| | | List<LocCache> locCacheList = this.selectList(rowWrapper); |
| | | |
| | | if (Cools.isEmpty(locCacheList)) { |
| | | return R.error("当前排没有找到库位"); |
| | | } |
| | | |
| | | // 批量更新库位状态 |
| | | Date now = new Date(); |
| | | int successCount = 0; |
| | | String locSts; |
| | | if (lock) { |
| | | // 锁定:满托设置为"F"(在库),空托设置为"D"(空桶/空栈板) |
| | | if (fullPlt == null) { |
| | | return R.error("锁定操作时,满托参数不能为空"); |
| | | } |
| | | locSts = fullPlt ? "F" : "D"; |
| | | } else { |
| | | // 解锁:统一设置为"O"(空库位) |
| | | locSts = "O"; |
| | | } |
| | | |
| | | for (LocCache cache : locCacheList) { |
| | | cache.setLocSts(locSts); |
| | | if (lock) { |
| | | cache.setFullPlt(fullPlt ? "Y" : "N"); |
| | | } else { |
| | | cache.setFullPlt("N"); |
| | | } |
| | | cache.setModiUser(userId); |
| | | cache.setModiTime(now); |
| | | if (this.updateById(cache)) { |
| | | successCount++; |
| | | } |
| | | } |
| | | |
| | | String action = lock ? "锁定" : "解锁"; |
| | | log.info("{}排{}所有库位成功,共{}个库位,操作人:{}", action, row, successCount, userId); |
| | | return R.ok(String.format("%s成功,共处理 %d 个库位", action, successCount)); |
| | | } catch (Exception e) { |
| | | log.error("锁定/解锁排库位失败", e); |
| | | return R.error("操作失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 锁定/解锁当前列的所有库位 |
| | | * @param locNo 库位号(用于获取列号bay1) |
| | | * @param lock 是否锁定,true-锁定,false-解锁 |
| | | * @param fullPlt 是否满托,true-满托(设置为F),false-空托(设置为D),解锁时忽略此参数 |
| | | * @param userId 用户ID |
| | | * @return 操作结果 |
| | | */ |
| | | @Override |
| | | public R lockOrUnlockBayLocCache(String locNo, Boolean lock, Boolean fullPlt, Long userId) { |
| | | try { |
| | | if (Cools.isEmpty(locNo)) { |
| | | return R.error("库位号不能为空"); |
| | | } |
| | | |
| | | // 根据库位号查询库位,获取列号 |
| | | EntityWrapper<LocCache> wrapper = new EntityWrapper<>(); |
| | | wrapper.eq("loc_no", locNo); |
| | | LocCache locCache = this.selectOne(wrapper); |
| | | |
| | | if (Cools.isEmpty(locCache)) { |
| | | return R.error("库位不存在:" + locNo); |
| | | } |
| | | |
| | | Integer bay = locCache.getBay1(); |
| | | if (Cools.isEmpty(bay)) { |
| | | return R.error("库位列号为空:" + locNo); |
| | | } |
| | | |
| | | // 查询当前列的所有库位 |
| | | EntityWrapper<LocCache> bayWrapper = new EntityWrapper<>(); |
| | | bayWrapper.eq("bay1", bay); |
| | | // 如果库位有库区ID,也加上库区条件 |
| | | if (!Cools.isEmpty(locCache.getAreaId())) { |
| | | bayWrapper.eq("area_id", locCache.getAreaId()); |
| | | } |
| | | List<LocCache> locCacheList = this.selectList(bayWrapper); |
| | | |
| | | if (Cools.isEmpty(locCacheList)) { |
| | | return R.error("当前列没有找到库位"); |
| | | } |
| | | |
| | | // 批量更新库位状态 |
| | | Date now = new Date(); |
| | | int successCount = 0; |
| | | String locSts; |
| | | if (lock) { |
| | | // 锁定:满托设置为"F"(在库),空托设置为"D"(空桶/空栈板) |
| | | if (fullPlt == null) { |
| | | return R.error("锁定操作时,满托参数不能为空"); |
| | | } |
| | | locSts = fullPlt ? "F" : "D"; |
| | | } else { |
| | | // 解锁:统一设置为"O"(空库位) |
| | | locSts = "O"; |
| | | } |
| | | |
| | | for (LocCache cache : locCacheList) { |
| | | cache.setLocSts(locSts); |
| | | if (lock) { |
| | | cache.setFullPlt(fullPlt ? "Y" : "N"); |
| | | } else { |
| | | cache.setFullPlt("N"); |
| | | } |
| | | cache.setModiUser(userId); |
| | | cache.setModiTime(now); |
| | | if (this.updateById(cache)) { |
| | | successCount++; |
| | | } |
| | | } |
| | | |
| | | String action = lock ? "锁定" : "解锁"; |
| | | log.info("{}列{}所有库位成功,共{}个库位,操作人:{}", action, bay, successCount, userId); |
| | | return R.ok(String.format("%s成功,共处理 %d 个库位", action, successCount)); |
| | | } catch (Exception e) { |
| | | log.error("锁定/解锁列库位失败", e); |
| | | return R.error("操作失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 清空整排的所有库位(所有列) |
| | | * @param locNo 库位号(用于获取排号row1) |
| | | * @param lock 是否锁定,true-锁定,false-解锁(清空) |
| | | * @param fullPlt 是否满托,true-满托(设置为F),false-空托(设置为D),解锁时忽略此参数 |
| | | * @param userId 用户ID |
| | | * @return 操作结果 |
| | | */ |
| | | @Override |
| | | public R clearAllColumnsInRow(String locNo, Boolean lock, Boolean fullPlt, Long userId) { |
| | | try { |
| | | if (Cools.isEmpty(locNo)) { |
| | | return R.error("库位号不能为空"); |
| | | } |
| | | |
| | | // 根据库位号查询库位,获取排号 |
| | | EntityWrapper<LocCache> wrapper = new EntityWrapper<>(); |
| | | wrapper.eq("loc_no", locNo); |
| | | LocCache locCache = this.selectOne(wrapper); |
| | | |
| | | if (Cools.isEmpty(locCache)) { |
| | | return R.error("库位不存在:" + locNo); |
| | | } |
| | | |
| | | Integer row = locCache.getRow1(); |
| | | if (Cools.isEmpty(row)) { |
| | | return R.error("库位排号为空:" + locNo); |
| | | } |
| | | |
| | | // 查询当前排的所有库位(所有列,不限制bay1) |
| | | EntityWrapper<LocCache> rowWrapper = new EntityWrapper<>(); |
| | | rowWrapper.eq("row1", row); |
| | | // 如果库位有库区ID,也加上库区条件 |
| | | if (!Cools.isEmpty(locCache.getAreaId())) { |
| | | rowWrapper.eq("area_id", locCache.getAreaId()); |
| | | } |
| | | List<LocCache> locCacheList = this.selectList(rowWrapper); |
| | | |
| | | if (Cools.isEmpty(locCacheList)) { |
| | | return R.error("当前排没有找到库位"); |
| | | } |
| | | |
| | | // 批量更新库位状态 |
| | | Date now = new Date(); |
| | | int successCount = 0; |
| | | String locSts; |
| | | if (lock) { |
| | | // 锁定:满托设置为"F"(在库),空托设置为"D"(空桶/空栈板) |
| | | if (fullPlt == null) { |
| | | return R.error("锁定操作时,满托参数不能为空"); |
| | | } |
| | | locSts = fullPlt ? "F" : "D"; |
| | | } else { |
| | | // 解锁:统一设置为"O"(空库位) |
| | | locSts = "O"; |
| | | } |
| | | |
| | | for (LocCache cache : locCacheList) { |
| | | cache.setLocSts(locSts); |
| | | if (lock) { |
| | | cache.setFullPlt(fullPlt ? "Y" : "N"); |
| | | } else { |
| | | cache.setFullPlt("N"); |
| | | } |
| | | cache.setModiUser(userId); |
| | | cache.setModiTime(now); |
| | | if (this.updateById(cache)) { |
| | | successCount++; |
| | | } |
| | | } |
| | | |
| | | String action = lock ? "锁定" : "清空"; |
| | | log.info("{}排{}所有库位(所有列)成功,共{}个库位,操作人:{}", action, row, successCount, userId); |
| | | return R.ok(String.format("%s成功,共处理 %d 个库位", action, successCount)); |
| | | } catch (Exception e) { |
| | | log.error("清空整排库位失败", e); |
| | | return R.error("操作失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | |
| | | String sourceSite = param.getSourceSite(); |
| | | String barcode = param.getBarcode(); |
| | | int ioType; |
| | | // 查询源站点(库位)信息,但不检查是否存在,允许下单成功 |
| | | // 站点不存在的检查将在定时任务(AgvHandler.callAgv)中进行 |
| | | LocCache locCache = locCacheService.selectOne(new EntityWrapper<LocCache>().eq("loc_no", sourceSite)); |
| | | if (null == locCache) { |
| | | throw new CoolException("站点不存在:" + sourceSite); |
| | | } |
| | | switch (type) { |
| | | case 1: |
| | | // 判断有没有组托 |
| | |
| | | if (count == 0) { |
| | | throw new CoolException("条码未组托:" + barcode); |
| | | } |
| | | ioType = 101; |
| | | ioType = 1; // AGV容器入库(实托入库) |
| | | |
| | | locCache.setLocSts(LocStsType.LOC_STS_TYPE_R.type); |
| | | locCacheService.updateById(locCache); |
| | | // 如果库位存在,更新状态为入库预约;不存在则跳过,由定时任务处理 |
| | | if (locCache != null) { |
| | | locCache.setLocSts(LocStsType.LOC_STS_TYPE_S.type); // S.入库预约 |
| | | locCacheService.updateById(locCache); |
| | | } |
| | | break; |
| | | case 2: |
| | | // 判断是拣选回库托盘 |
| | |
| | | if (wrkMast.getIoType() != 103 && wrkMast.getIoType() != 107) { |
| | | throw new CoolException("条码不需要回库:" + barcode); |
| | | } |
| | | ioType = wrkMast.getIoType() - 50; |
| | | ioType = wrkMast.getIoType() - 50; // 103->53(拣料入库), 107->57(盘点入库) |
| | | |
| | | locCache.setLocSts(LocStsType.LOC_STS_TYPE_R.type); |
| | | locCacheService.updateById(locCache); |
| | | // 如果库位存在,更新状态为入库预约;不存在则跳过,由定时任务处理 |
| | | if (locCache != null) { |
| | | locCache.setLocSts(LocStsType.LOC_STS_TYPE_S.type); // S.入库预约(容器回库是入库操作) |
| | | locCacheService.updateById(locCache); |
| | | } |
| | | break; |
| | | case 3: |
| | | // 判断是否为空托入库:检查条码在wms中不存在,确认为空托盘 |
| | |
| | | } |
| | | |
| | | // 根据whs_type和库位编号前缀选择站点和机器人组 |
| | | Long whsType = locCache.getWhsType(); |
| | | String locNo = locCache.getLocNo(); |
| | | // 如果库位不存在,使用默认逻辑(根据type判断),站点不存在的检查将在定时任务中进行 |
| | | Long whsType = locCache != null ? locCache.getWhsType() : null; |
| | | String locNo = locCache != null ? locCache.getLocNo() : sourceSite; |
| | | List<String> targetStations; |
| | | String robotGroup; |
| | | |
| | |
| | | agvProperties.getWestDisplayName(), robotGroup); |
| | | } else { |
| | | // whs_type为空或其他值,根据type判断(兼容旧逻辑) |
| | | // 如果库位不存在,也使用此逻辑 |
| | | if (type == 1) { |
| | | targetStations = agvProperties.getEastStations(); |
| | | robotGroup = agvProperties.getRobotGroupEast(); |
| | |
| | | targetStations = agvProperties.getWestStations(); |
| | | robotGroup = agvProperties.getRobotGroupWest(); |
| | | } |
| | | log.warn("库位whs_type={}未配置或不在映射范围内,使用type={}的默认逻辑", whsType, type); |
| | | if (locCache == null) { |
| | | log.warn("源站点(库位){}不存在,使用type={}的默认逻辑,站点检查将在定时任务中进行", sourceSite, type); |
| | | } else { |
| | | log.warn("库位whs_type={}未配置或不在映射范围内,使用type={}的默认逻辑", whsType, type); |
| | | } |
| | | } |
| | | |
| | | // 将站点字符串列表转换为整数列表 |
| | |
| | | .map(Integer::parseInt) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 判断能入站点(in_enable="Y"表示能入) |
| | | // 判断能入站点(in_enable="Y"表示能入),排除dev_no=0的无效站点 |
| | | // 注意:不在此处检查站点是否存在或可用,允许下单成功 |
| | | // 站点检查和分配将在定时任务(AgvHandler.callAgv)中进行 |
| | | List<Integer> sites = basDevpMapper.selectList( |
| | | new EntityWrapper<BasDevp>() |
| | | .eq("in_enable", "Y") // in_enable是能入 |
| | | .in("dev_no", siteIntList) |
| | | ).stream().map(BasDevp::getDevNo).collect(Collectors.toList()); |
| | | |
| | | if (sites.isEmpty()) { |
| | | throw new CoolException("没有能入站点,whs_type:" + whsType + ",type:" + type); |
| | | } |
| | | .ne("dev_no", 0) // 排除dev_no=0的无效站点 |
| | | ).stream() |
| | | .map(BasDevp::getDevNo) |
| | | .filter(devNo -> devNo != null && devNo != 0) // 再次过滤,确保不为null或0 |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 获取没有出库任务的站点 |
| | | List<Integer> canInSites = basDevpMapper.getCanInSites(sites); |
| | | if (canInSites.isEmpty()) { |
| | | throw new CoolException("请等待出库完成,type:" + type); |
| | | } |
| | | List<Integer> canInSites = sites.isEmpty() ? new ArrayList<>() : basDevpMapper.getCanInSites(sites); |
| | | |
| | | // 寻找入库任务最少的站点(且必须in_enable="Y"能入 和 canining="Y"可入) |
| | | // 寻找入库任务最少的站点(且必须in_enable="Y"能入 和 canining="Y"可入),排除dev_no=0的无效站点 |
| | | // 注意:不在此处检查未完成的AGV任务,允许PDA持续申请下单 |
| | | // 未完成任务的检查将在发送AGV请求时进行(AgvHandler.callAgv) |
| | | List<BasDevp> devList = basDevpMapper.selectList(new EntityWrapper<BasDevp>() |
| | | List<BasDevp> devList = canInSites.isEmpty() ? new ArrayList<>() : basDevpMapper.selectList(new EntityWrapper<BasDevp>() |
| | | .in("dev_no", canInSites) |
| | | .eq("in_enable", "Y") // in_enable是能入 |
| | | .eq("canining", "Y") // canining是可入 |
| | | ); |
| | | .ne("dev_no", 0) // 排除dev_no=0的无效站点 |
| | | ).stream() |
| | | .filter(dev -> dev.getDevNo() != null && dev.getDevNo() != 0) // 再次过滤,确保不为null或0 |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 选择站点(如果可入站点为空,则不在创建任务时分配站点,由定时任务分配) |
| | | Integer endSite = null; |
| | |
| | | } |
| | | |
| | | endSite = basDevp.getDevNo(); |
| | | |
| | | // 入库暂存+1 |
| | | basDevpMapper.incrementInQty(endSite); |
| | | |
| | | // 检查站点是否有效(不能为0或null) |
| | | if (endSite == null || endSite == 0) { |
| | | log.error("分配的站点无效(dev_no={}),不分配站点,将在定时任务中分配", endSite); |
| | | endSite = null; // 设置为null,由定时任务分配 |
| | | } else { |
| | | // 入库暂存+1 |
| | | basDevpMapper.incrementInQty(endSite); |
| | | } |
| | | } else { |
| | | // 没有可入站点,记录日志但不阻止下单,站点分配将在定时任务中处理 |
| | | String groupName = (whsType != null && whsType.equals(agvProperties.getWhsTypeMapping().getInboundArea())) |
| | | ? agvProperties.getEastDisplayName() : agvProperties.getWestDisplayName(); |
| | | log.warn("{}可用站点({})中没有可入站点(in_enable='Y'且canining='Y'),暂不分配站点,将在定时任务中分配", groupName, canInSites); |
| | | // log.warn("{}可用站点({})中没有可入站点(in_enable='Y'且canining='Y'),暂不分配站点,将在定时任务中分配", groupName, canInSites); |
| | | } |
| | | |
| | | |
| | |
| | | .setStaNo(endSite != null ? String.valueOf(endSite) : null) // 如果分配了站点则设置,否则为null,由定时任务分配 |
| | | .setSourceStaNo(sourceSite) // 设置源站点 |
| | | .setInvWh(robotGroup) // 根据whs_type设置机器人组 |
| | | .setFullPlt(ioType != 10 ? "N" : "Y")// 满板:Y |
| | | .setFullPlt(ioType == 10 ? "N" : "Y")// 空托入库(ioType=10)设置为N,其他入库设置为Y(满板) |
| | | .setPicking("N") // 拣料 |
| | | .setExitMk("N")// 退出 |
| | | .setSourceLocNo(locCache.getLocNo()) // 设置源库位编号,用于AGV fromBin |
| | | .setEmptyMk(ioType == 10 ? "Y" : "N")// 空板 |
| | | .setSourceLocNo(locCache != null ? locCache.getLocNo() : sourceSite) // 设置源库位编号,用于AGV fromBin,如果库位不存在则使用sourceSite |
| | | .setEmptyMk(ioType == 10 ? "Y" : "N")// 空托入库(ioType=10)设置为Y,其他设置为N |
| | | .setBarcode(barcode)// 托盘码 |
| | | .setLinkMis("N") |
| | | .setAppeTime(now) |
| | |
| | | throw new CoolException("保存工作档失败"); |
| | | } |
| | | |
| | | // 更新暂存位状态为 R.出库预约 |
| | | basStationMapper.updateLocStsBatch( Collections.singletonList(String.valueOf(sourceSite)), "R"); |
| | | // 如果库位存在,根据ioType更新暂存位状态:入库任务设置为S(入库预约),出库任务设置为R(出库预约) |
| | | if (locCache != null) { |
| | | String locSts = (ioType < 100) ? "S" : "R"; // 入库任务(ioType < 100)设置为S,出库任务设置为R |
| | | basStationMapper.updateLocStsBatch( Collections.singletonList(String.valueOf(sourceSite)), locSts); |
| | | } |
| | | return R.ok("agv任务生成成功!"); |
| | | |
| | | } |
| | |
| | | import com.zy.asrs.entity.TaskLog; |
| | | import com.zy.asrs.entity.WrkMast; |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.entity.LocCache; |
| | | import com.zy.asrs.mapper.BasDevpMapper; |
| | | import com.zy.asrs.mapper.BasStationMapper; |
| | | import com.zy.asrs.mapper.WrkMastMapper; |
| | | import com.zy.asrs.service.ApiLogService; |
| | | import com.zy.asrs.service.LocCacheService; |
| | | import com.zy.asrs.service.TaskLogService; |
| | | import com.zy.asrs.service.TaskService; |
| | | import com.zy.common.constant.ApiInterfaceConstant; |
| | |
| | | private BasDevpMapper basDevpMapper; |
| | | |
| | | @Resource |
| | | private LocCacheService locCacheService; |
| | | |
| | | @Resource |
| | | private AgvProperties agvProperties; |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | for (Task task : taskList) { |
| | | // 如果任务没有分配站点,先分配站点 |
| | | // 如果任务状态已经是8(已呼叫AGV,正在搬运),则不再发送指令 |
| | | if (task.getWrkSts() != null && task.getWrkSts() == 8L) { |
| | | log.debug("任务ID:{}状态已是8(正在搬运),跳过发送", task.getId()); |
| | | continue; |
| | | } |
| | | |
| | | // 如果任务没有分配站点,先分配站点(只有为空时才分配,已经分配了不要清空) |
| | | String staNo = task.getStaNo(); |
| | | if (staNo == null || staNo.isEmpty()) { |
| | | Integer allocatedSite = allocateSiteForTask(task); |
| | | if (allocatedSite == null) { |
| | | log.warn("任务ID:{}无法分配站点,跳过本次发送", task.getId()); |
| | | continue; // 无法分配站点,跳过本次发送 |
| | | String errorMsg = allocateSiteForTask(task); |
| | | if (errorMsg != null) { |
| | | // 无法分配站点,只在日志中记录,不记录到任务中(app不需要知道) |
| | | log.warn("任务ID:{}无法分配站点:{}", task.getId(), errorMsg); |
| | | continue; |
| | | } |
| | | staNo = String.valueOf(allocatedSite); |
| | | task.setStaNo(staNo); |
| | | taskService.updateById(task); |
| | | // 检查是否成功分配了站点(如果返回null且staNo仍为空,说明所有站点都在搬运,等待下次再试) |
| | | staNo = task.getStaNo(); |
| | | if (staNo == null || staNo.isEmpty()) { |
| | | // 所有站点都在搬运,暂不分配,等待下次定时任务再尝试 |
| | | continue; |
| | | } |
| | | log.info("任务ID:{}已分配站点:{}", task.getId(), staNo); |
| | | } |
| | | |
| | | // 检查目标站点是否有正在搬运的同类型AGV任务(出库和入库互不干扰) |
| | | // 只有状态8(已呼叫AGV,正在搬运)的任务才会阻塞,状态7(待呼叫)的任务不阻塞 |
| | | // 这样可以避免所有任务都卡在呼叫状态,按id最小的优先呼叫 |
| | | // 检查目标站点是否有效(不为0且存在) |
| | | if (staNo != null && !staNo.isEmpty()) { |
| | | try { |
| | | Integer siteNo = Integer.parseInt(staNo); |
| | | // 检查站点是否为0(无效站点),如果是0则不发送,但不清空站点 |
| | | if (siteNo == null || siteNo == 0) { |
| | | log.warn("任务ID:{}的目标站点{}无效(为0),跳过发送,保留站点分配", task.getId(), staNo); |
| | | continue; |
| | | } |
| | | List<BasDevp> basDevpList = basDevpMapper.selectList(new EntityWrapper<BasDevp>().eq("dev_no", siteNo)); |
| | | if (basDevpList == null || basDevpList.isEmpty()) { |
| | | // 站点不存在,跳过发送,不清空站点 |
| | | log.warn("任务ID:{}的目标站点{}不存在,跳过发送,保留站点分配", task.getId(), staNo); |
| | | continue; |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | // 站点格式错误,跳过发送,不清空站点 |
| | | log.warn("任务ID:{}的目标站点{}格式错误,跳过发送,保留站点分配", task.getId(), staNo); |
| | | continue; |
| | | } |
| | | } else { |
| | | // 没有站点,跳过 |
| | | continue; |
| | | } |
| | | |
| | | // 检查站点是否有状态8的同类型任务,有则跳过(不清空站点) |
| | | if (staNo != null && !staNo.isEmpty() && task.getIoType() != null) { |
| | | // 根据当前任务类型,只检查同类型的正在搬运任务(状态8) |
| | | // 入库任务(ioType < 100):只检查入库类型的正在搬运任务 |
| | |
| | | taskType = "出库"; |
| | | } |
| | | |
| | | // 只检查状态为8(已呼叫AGV,正在搬运)的同类型任务 |
| | | // 检查状态为8(已呼叫AGV,正在搬运)的同类型任务 |
| | | List<Task> transportingTasks = taskService.selectList( |
| | | new EntityWrapper<Task>() |
| | | .eq("sta_no", staNo) |
| | |
| | | ); |
| | | |
| | | if (!transportingTasks.isEmpty()) { |
| | | log.info("站点{}有{}个正在搬运的{}AGV任务,跳过本次发送,等待搬运完成。当前任务ID:{}", |
| | | log.info("站点{}有{}个正在搬运的{}AGV任务,跳过当前任务ID:{}", |
| | | staNo, transportingTasks.size(), taskType, task.getId()); |
| | | continue; // 跳过本次发送,等待下次 |
| | | continue; |
| | | } |
| | | } |
| | | |
| | |
| | | default: |
| | | } |
| | | String body = getRequest(task,namespace); |
| | | // 打印请求信息 |
| | | log.info("{}呼叫agv搬运 - 请求地址:{}", namespace, url); |
| | | |
| | | // 获取当前重试次数 |
| | | int currentRetryCount = getRetryCount(task); |
| | | int maxRetryCount = agvProperties.getCallRetry().getMaxRetryCount(); |
| | | boolean retryEnabled = agvProperties.getCallRetry().isEnabled(); |
| | | |
| | | // 如果重试次数已达到最大值,跳过本次发送 |
| | | if (retryEnabled && currentRetryCount >= maxRetryCount) { |
| | | log.warn("{}呼叫agv搬运 - 任务ID:{}已达到最大重试次数({}),停止重试", |
| | | namespace, task.getId(), maxRetryCount); |
| | | // 记录最终失败信息 |
| | | task.setErrorTime(new Date()); |
| | | task.setErrorMemo(String.format("AGV呼叫失败,已达到最大重试次数(%d次)", maxRetryCount)); |
| | | taskService.updateById(task); |
| | | continue; |
| | | } |
| | | |
| | | // 打印请求信息(包含重试次数) |
| | | if (currentRetryCount > 0) { |
| | | log.info("{}呼叫agv搬运(第{}次重试) - 请求地址:{}", namespace, currentRetryCount + 1, url); |
| | | } else { |
| | | log.info("{}呼叫agv搬运 - 请求地址:{}", namespace, url); |
| | | } |
| | | log.info("{}呼叫agv搬运 - 请求参数:{}", namespace, body); |
| | | |
| | | try { |
| | | // 使用仙工M4接口 |
| | | response = new HttpHandler.Builder() |
| | |
| | | |
| | | // 检查响应是否为空 |
| | | if (response == null || response.trim().isEmpty()) { |
| | | log.error("{}呼叫agv搬运失败 - 任务ID:{},AGV接口返回为空", namespace, task.getId()); |
| | | String errorMsg = "AGV接口返回为空"; |
| | | log.error("{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, task.getId(), errorMsg); |
| | | handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount); |
| | | continue; |
| | | } |
| | | |
| | | JSONObject jsonObject = JSON.parseObject(response); |
| | | if (jsonObject == null) { |
| | | log.error("{}呼叫agv搬运失败 - 任务ID:{},响应JSON解析失败,响应内容:{}", namespace, task.getId(), response); |
| | | String errorMsg = "响应JSON解析失败,响应内容:" + response; |
| | | log.error("{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, task.getId(), errorMsg); |
| | | handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount); |
| | | continue; |
| | | } |
| | | |
| | | Integer code = jsonObject.getInteger("code"); |
| | | if (code != null && code.equals(200)) { |
| | | // 呼叫成功,清除重试次数和错误信息 |
| | | success = true; |
| | | task.setWrkSts(8L); |
| | | task.setMemo(clearRetryInfo(task.getMemo())); // 清除重试信息 |
| | | task.setErrorTime(null); |
| | | task.setErrorMemo(null); |
| | | taskService.updateById(task); |
| | | log.info("{}呼叫agv搬运成功 - 任务ID:{}", namespace, task.getId()); |
| | | } else { |
| | | String message = jsonObject.getString("message"); |
| | | log.error("{}呼叫agv搬运失败 - 任务ID:{},错误码:{},错误信息:{}", |
| | | namespace, task.getId(), code, message); |
| | | String errorMsg = String.format("错误码:%s,错误信息:%s", code, message); |
| | | log.error("{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, task.getId(), errorMsg); |
| | | handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("{}呼叫agv搬运异常 - 任务ID:{},请求地址:{},请求参数:{},异常信息:{}", |
| | | namespace, task.getId(), url, body, e.getMessage(), e); |
| | | String errorMsg = "异常信息:" + e.getMessage(); |
| | | log.error("{}呼叫agv搬运异常 - 任务ID:{},请求地址:{},请求参数:{},{}", |
| | | namespace, task.getId(), url, body, errorMsg, e); |
| | | handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount); |
| | | } finally { |
| | | try { |
| | | // 保存接口日志 |
| | |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 处理AGV呼叫失败的情况 |
| | | * @param task 任务对象 |
| | | * @param namespace 命名空间(入库/出库/转移) |
| | | * @param errorMsg 错误信息 |
| | | * @param retryEnabled 是否启用重试 |
| | | * @param maxRetryCount 最大重试次数 |
| | | * @param currentRetryCount 当前重试次数 |
| | | */ |
| | | private void handleCallFailure(Task task, String namespace, String errorMsg, |
| | | boolean retryEnabled, int maxRetryCount, int currentRetryCount) { |
| | | if (retryEnabled && currentRetryCount < maxRetryCount) { |
| | | // 增加重试次数 |
| | | int newRetryCount = currentRetryCount + 1; |
| | | task.setMemo(updateRetryCount(task.getMemo(), newRetryCount)); |
| | | task.setErrorTime(new Date()); |
| | | task.setErrorMemo(String.format("AGV呼叫失败(第%d次重试):%s", newRetryCount, errorMsg)); |
| | | taskService.updateById(task); |
| | | log.info("{}呼叫agv搬运失败 - 任务ID:{},已重试{}次,将在下次定时任务时继续重试(最多{}次)", |
| | | namespace, task.getId(), newRetryCount, maxRetryCount); |
| | | } else { |
| | | // 不启用重试或已达到最大重试次数,停止重试 |
| | | task.setErrorTime(new Date()); |
| | | if (retryEnabled) { |
| | | task.setErrorMemo(String.format("AGV呼叫失败,已达到最大重试次数(%d次):%s", maxRetryCount, errorMsg)); |
| | | } else { |
| | | task.setErrorMemo(String.format("AGV呼叫失败(重试未启用):%s", errorMsg)); |
| | | } |
| | | taskService.updateById(task); |
| | | log.warn("{}呼叫agv搬运失败 - 任务ID:{},停止重试。错误信息:{}", |
| | | namespace, task.getId(), errorMsg); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从memo字段中获取重试次数 |
| | | * memo格式:如果包含"retryCount:数字",则返回该数字,否则返回0 |
| | | * @param task 任务对象 |
| | | * @return 重试次数 |
| | | */ |
| | | private int getRetryCount(Task task) { |
| | | String memo = task.getMemo(); |
| | | if (memo == null || memo.trim().isEmpty()) { |
| | | return 0; |
| | | } |
| | | try { |
| | | // 查找 "retryCount:数字" 格式 |
| | | String prefix = "retryCount:"; |
| | | int index = memo.indexOf(prefix); |
| | | if (index >= 0) { |
| | | int startIndex = index + prefix.length(); |
| | | int endIndex = memo.indexOf(",", startIndex); |
| | | if (endIndex < 0) { |
| | | endIndex = memo.length(); |
| | | } |
| | | String countStr = memo.substring(startIndex, endIndex).trim(); |
| | | return Integer.parseInt(countStr); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("解析任务ID:{}的重试次数失败,memo:{}", task.getId(), memo, e); |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * 更新memo字段中的重试次数 |
| | | * @param memo 原始memo内容 |
| | | * @param retryCount 新的重试次数 |
| | | * @return 更新后的memo内容 |
| | | */ |
| | | private String updateRetryCount(String memo, int retryCount) { |
| | | if (memo == null) { |
| | | memo = ""; |
| | | } |
| | | // 移除旧的retryCount信息 |
| | | String cleanedMemo = clearRetryInfo(memo); |
| | | // 添加新的retryCount信息 |
| | | if (cleanedMemo.isEmpty()) { |
| | | return "retryCount:" + retryCount; |
| | | } else { |
| | | return cleanedMemo + ",retryCount:" + retryCount; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 清除memo字段中的重试信息 |
| | | * @param memo 原始memo内容 |
| | | * @return 清除后的memo内容 |
| | | */ |
| | | private String clearRetryInfo(String memo) { |
| | | if (memo == null || memo.trim().isEmpty()) { |
| | | return ""; |
| | | } |
| | | // 移除 "retryCount:数字" 格式的内容 |
| | | String result = memo.replaceAll("retryCount:\\d+", "").trim(); |
| | | // 清理多余的逗号 |
| | | result = result.replaceAll("^,|,$", "").replaceAll(",,+", ","); |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | |
| | | /** |
| | | * 为任务分配站点(定时任务中调用) |
| | | * @param task 任务对象 |
| | | * @return 分配的站点编号,如果无法分配则返回null |
| | | * @return 如果无法分配站点,返回错误信息;如果分配成功,返回null并更新task的staNo |
| | | */ |
| | | private Integer allocateSiteForTask(Task task) { |
| | | private String allocateSiteForTask(Task task) { |
| | | // 根据任务的invWh(机器人组)判断是东侧还是西侧 |
| | | String robotGroup = task.getInvWh(); |
| | | List<String> targetStations; |
| | |
| | | } |
| | | |
| | | if (targetStations.isEmpty()) { |
| | | log.warn("任务ID:{}没有可用的目标站点配置", task.getId()); |
| | | return null; |
| | | String errorMsg = "没有可用的目标站点配置"; |
| | | log.warn("任务ID:{}", errorMsg, task.getId()); |
| | | return errorMsg; |
| | | } |
| | | |
| | | // 将站点字符串列表转换为整数列表 |
| | |
| | | .map(Integer::parseInt) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 判断能入站点(in_enable="Y"表示能入) |
| | | // 判断能入站点(in_enable="Y"表示能入),排除dev_no=0的无效站点 |
| | | List<Integer> sites = basDevpMapper.selectList( |
| | | new EntityWrapper<BasDevp>() |
| | | .eq("in_enable", "Y") |
| | | .in("dev_no", siteIntList) |
| | | ).stream().map(BasDevp::getDevNo).collect(Collectors.toList()); |
| | | .ne("dev_no", 0) // 排除dev_no=0的无效站点 |
| | | ).stream() |
| | | .map(BasDevp::getDevNo) |
| | | .filter(devNo -> devNo != null && devNo != 0) // 再次过滤,确保不为null或0 |
| | | .collect(Collectors.toList()); |
| | | |
| | | if (sites.isEmpty()) { |
| | | log.warn("任务ID:{}没有能入站点", task.getId()); |
| | | return null; |
| | | String errorMsg = "没有能入站点"; |
| | | log.warn("任务ID:{}", errorMsg, task.getId()); |
| | | return errorMsg; |
| | | } |
| | | |
| | | // 获取没有出库任务的站点 |
| | | List<Integer> canInSites = basDevpMapper.getCanInSites(sites); |
| | | if (canInSites.isEmpty()) { |
| | | log.warn("任务ID:{}没有可入站点(请等待出库完成)", task.getId()); |
| | | return null; |
| | | String errorMsg = "请等待出库完成"; |
| | | log.warn("任务ID:{}没有可入站点({})", task.getId(), errorMsg); |
| | | return errorMsg; |
| | | } |
| | | |
| | | // 寻找入库任务最少的站点(且必须in_enable="Y"能入 和 canining="Y"可入) |
| | | // 寻找入库任务最少的站点(且必须in_enable="Y"能入 和 canining="Y"可入),排除dev_no=0的无效站点 |
| | | List<BasDevp> devList = basDevpMapper.selectList(new EntityWrapper<BasDevp>() |
| | | .in("dev_no", canInSites) |
| | | .eq("in_enable", "Y") |
| | | .eq("canining", "Y") |
| | | ); |
| | | .ne("dev_no", 0) // 排除dev_no=0的无效站点 |
| | | ).stream() |
| | | .filter(dev -> dev.getDevNo() != null && dev.getDevNo() != 0) // 再次过滤,确保不为null或0 |
| | | .collect(Collectors.toList()); |
| | | |
| | | if (devList.isEmpty()) { |
| | | log.warn("任务ID:{}没有可入站点(in_enable='Y'且canining='Y')", task.getId()); |
| | | return null; |
| | | String errorMsg = "没有可入站点(in_enable='Y'且canining='Y')"; |
| | | log.warn("任务ID:{}", errorMsg, task.getId()); |
| | | return errorMsg; |
| | | } |
| | | |
| | | // 先按规则排序(入库任务数排序) |
| | |
| | | break; |
| | | } |
| | | |
| | | // 如果所有站点都在搬运,则不分配站点 |
| | | // 如果所有站点都在搬运,则不分配站点(只在定时任务中记录日志,不返回错误信息) |
| | | if (selectedSite == null) { |
| | | log.warn("任务ID:{}的所有候选站点都有正在搬运的{}任务,暂不分配站点,等待空闲", |
| | | task.getId(), taskIoType != null && taskIoType < 100 ? "入库" : "出库"); |
| | | return null; |
| | | // log.warn("任务ID:{},暂不分配站点,等待空闲 - 所有候选站点都有正在搬运的{}任务", |
| | | // task.getId(), taskIoType != null && taskIoType < 100 ? "入库" : "出库"); |
| | | return null; // 返回null,表示暂不分配,等待下次定时任务再尝试 |
| | | } |
| | | |
| | | Integer endSite = selectedSite.getDevNo(); |
| | | |
| | | // 检查站点是否有效(不能为0或null) |
| | | if (endSite == null || endSite == 0) { |
| | | String errorMsg = String.format("分配的站点无效(dev_no=%s)", endSite); |
| | | log.error("任务ID:{},{}", task.getId(), errorMsg); |
| | | return errorMsg; |
| | | } |
| | | |
| | | // 入库暂存+1 |
| | | basDevpMapper.incrementInQty(endSite); |
| | | |
| | | // 更新任务的站点编号 |
| | | task.setStaNo(String.valueOf(endSite)); |
| | | taskService.updateById(task); |
| | | |
| | | log.info("任务ID:{}已分配站点:{}", task.getId(), endSite); |
| | | return endSite; |
| | | return null; // 分配成功,返回null |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | // 分配缓存库位:只查找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:{}", |
| | |
| | | } |
| | | |
| | | /** |
| | | * 按优先级分配缓存库位 |
| | | * 优先级规则: |
| | | * 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; |
| | | } |
| | | |
| | | /** |
| | | * 为出库到缓存区的任务分配站点(使用和入库一样的分配策略) |
| | | * @param cacheStations 缓存区站点列表 |
| | | * @param ioType 任务类型(101=全板出库,110=空板出库) |
| | |
| | | |
| | | // 如果所有站点都在搬运,则不分配站点 |
| | | if (selectedSite == null) { |
| | | log.warn("所有缓存区站点都有正在搬运的出库任务,暂不分配站点,等待空闲"); |
| | | // log.warn("所有缓存区站点都有正在搬运的出库任务,暂不分配站点,等待空闲"); |
| | | return null; |
| | | } |
| | | |
| | |
| | | } |
| | | }); |
| | | |
| | | 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(库存更新完成) |
| | |
| | | private LocationPrefix locationPrefix = new LocationPrefix(); |
| | | |
| | | /** |
| | | * AGV呼叫重试配置 |
| | | */ |
| | | private AgvCallRetry callRetry = new AgvCallRetry(); |
| | | |
| | | /** |
| | | * whs_type映射配置内部类 |
| | | */ |
| | | @Data |
| | |
| | | */ |
| | | private String cacheArea = "WA"; |
| | | } |
| | | |
| | | /** |
| | | * AGV呼叫重试配置内部类 |
| | | */ |
| | | @Data |
| | | public static class AgvCallRetry { |
| | | /** |
| | | * 是否启用重试机制 |
| | | * true: 启用重试,失败后会自动重试 |
| | | * false: 不启用重试,失败后直接停止(默认) |
| | | */ |
| | | private boolean enabled = false; |
| | | |
| | | /** |
| | | * 最大重试次数 |
| | | * 当呼叫AGV失败时,最多重试多少次后停止 |
| | | * 默认值:3次 |
| | | */ |
| | | private int maxRetryCount = 3; |
| | | |
| | | /** |
| | | * 重试间隔时间(秒) |
| | | * 每次重试之间的等待时间 |
| | | * 默认值:5秒 |
| | | */ |
| | | private int retryIntervalSeconds = 5; |
| | | } |
| | | } |
| | |
| | | |
| | | Agv: |
| | | sendTask: false |
| | | # AGV呼叫重试配置 |
| | | callRetry: |
| | | # 是否启用重试机制 |
| | | # true: 启用重试,失败后会自动重试 |
| | | # false: 不启用重试,失败后直接停止 |
| | | enabled: true |
| | | # 最大重试次数(失败后最多重试多少次后停止) |
| | | maxRetryCount: 3 |
| | | # 重试间隔时间(秒,每次重试之间的等待时间) |
| | | retryIntervalSeconds: 5 |
| | | # 东侧配置 |
| | | east: |
| | | robotGroup: "Group-001" |
| | |
| | | inboundOnly: "CA" |
| | | # WA前缀:会被出库分配缓存区的库位前缀 |
| | | cacheArea: "WA" |
| | | # 缓存库位分配规则配置 |
| | | cacheLocationAllocation: |
| | | # 分配优先级说明: |
| | | # 优先级1:分配第三列(bay1=3),且该排的1、2、3列都是空的 |
| | | # 优先级2:分配第二列(bay1=2),且该排的1、2列都是空的 |
| | | # 优先级3:分配第一列(bay1=1),所有排的第二第三列都满了 |
| | | # 优先级4:如果所有第一列都满了,再检查第二列 |
| | | # 优先级5:最后检查第三列 |
| | | # 层(lev1)从第一层开始 |
| | | priority: |
| | | # 列优先级顺序(从高到低) |
| | | bayPriority: |
| | | - 3 # 第三列优先级最高 |
| | | - 2 # 第二列 |
| | | - 1 # 第一列 |
| | | # 层优先级:从第一层开始 |
| | | levStart: 1 |
| | | # 是否要求排的所有列都为空才分配(优先级1和2的要求) |
| | | requireAllColumnsEmpty: true |
| | | |
| | | # 越库配置 |
| | | cross-dock: |
| | |
| | | , { field: 'appeTime$', align: 'center', title: '添加时间', hide: true } |
| | | , { field: 'frozen', align: 'center', title: '是否冻结', hide: true } |
| | | , { field: 'frozenMemo', align: 'center', title: '冻结备注', hide: true } |
| | | , { fixed: 'right', title: '操作', align: 'center', toolbar: '#operate', width: 200 }] |
| | | , { fixed: 'right', title: '操作', align: 'center', toolbar: '#operate', width: 350 }] |
| | | ], |
| | | request: { |
| | | pageName: 'curr', pageSize: 'limit' |
| | |
| | | break; |
| | | case "del": |
| | | del([data.id]); |
| | | break; |
| | | case "clearLoc": |
| | | clearLocation(data); |
| | | break; |
| | | case "setInStock": |
| | | setInStock(data); |
| | | break; |
| | | } |
| | | }); |
| | |
| | | |
| | | layDateRender(); |
| | | |
| | | // 清空库位 |
| | | function clearLocation(data) { |
| | | layer.confirm('确认清空库位:' + data.locNo + '?', { |
| | | skin: 'layui-layer-admin', shade: .1 |
| | | }, function (i) { |
| | | layer.close(i); |
| | | var loadIndex = layer.load(2); |
| | | $.ajax({ |
| | | url: baseUrl + "/locCache/lockOrUnlock/auth", |
| | | headers: { 'token': localStorage.getItem('token') }, |
| | | data: { |
| | | locNo: data.locNo, |
| | | lock: false // false表示解锁/清空 |
| | | }, |
| | | method: 'POST', |
| | | success: function (res) { |
| | | layer.close(loadIndex); |
| | | if (res.code === 200) { |
| | | layer.msg(res.msg || '清空库位成功', { icon: 1 }); |
| | | tableReload(); |
| | | } else if (res.code === 403) { |
| | | top.location.href = baseUrl + "/"; |
| | | } else { |
| | | layer.msg(res.msg || '清空库位失败', { icon: 2 }); |
| | | } |
| | | } |
| | | }) |
| | | }); |
| | | } |
| | | |
| | | // 改为有货状态 |
| | | function setInStock(data) { |
| | | layer.prompt({ |
| | | title: '请选择满托/空托', |
| | | formType: 2, |
| | | content: '<div style="padding: 20px;"><label><input type="radio" name="fullPlt" value="true" checked> 满托</label><br><br><label><input type="radio" name="fullPlt" value="false"> 空托</label></div>', |
| | | area: ['300px', '200px'] |
| | | }, function(value, index, elem){ |
| | | var fullPlt = $(elem).find('input[name="fullPlt"]:checked').val() === 'true'; |
| | | layer.close(index); |
| | | var loadIndex = layer.load(2); |
| | | $.ajax({ |
| | | url: baseUrl + "/locCache/lockOrUnlock/auth", |
| | | headers: { 'token': localStorage.getItem('token') }, |
| | | data: { |
| | | locNo: data.locNo, |
| | | lock: true, // true表示锁定/改为有货 |
| | | fullPlt: fullPlt |
| | | }, |
| | | method: 'POST', |
| | | success: function (res) { |
| | | layer.close(loadIndex); |
| | | if (res.code === 200) { |
| | | layer.msg(res.msg || '改为有货状态成功', { icon: 1 }); |
| | | tableReload(); |
| | | } else if (res.code === 403) { |
| | | top.location.href = baseUrl + "/"; |
| | | } else { |
| | | layer.msg(res.msg || '改为有货状态失败', { icon: 2 }); |
| | | } |
| | | } |
| | | }) |
| | | }); |
| | | } |
| | | |
| | | }); |
| | | |
| | | // 关闭动作 |
| | |
| | | form.on('submit(save)', function () { |
| | | var param = []; |
| | | var checkData = tree.getChecked('powerTree'); |
| | | if (!checkData || !Array.isArray(checkData)) { |
| | | layer.msg('没有选中的权限数据'); |
| | | return false; |
| | | } |
| | | checkData.map(function (obj) { |
| | | obj.children.map(function (resource) { |
| | | |
| | | var childrens = []; |
| | | resource.children.map(function (resource) { |
| | | childrens.push(resource.id); |
| | | // 检查 obj.children 是否存在且为数组 |
| | | if (obj.children && Array.isArray(obj.children)) { |
| | | obj.children.map(function (resource) { |
| | | var childrens = []; |
| | | // 检查 resource.children 是否存在且为数组 |
| | | if (resource.children && Array.isArray(resource.children)) { |
| | | resource.children.map(function (resource) { |
| | | childrens.push(resource.id); |
| | | }); |
| | | } |
| | | var one = { |
| | | 'two': resource.id, |
| | | 'three': childrens |
| | | }; |
| | | param.push(one); |
| | | }); |
| | | var one = { |
| | | 'two': resource.id, |
| | | 'three': childrens |
| | | }; |
| | | param.push(one); |
| | | }) |
| | | } |
| | | }); |
| | | $.ajax({ |
| | | url: baseUrl+"/power/auth", |
| | |
| | | <script type="text/html" id="operate"> |
| | | <a class="layui-btn layui-btn-xs btn-detlShow" lay-event="showDetl">明细</a> |
| | | <a class="layui-btn layui-btn-primary layui-btn-xs btn-edit" lay-event="edit">修改</a> |
| | | <a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="clearLoc">清空库位</a> |
| | | <a class="layui-btn layui-btn-xs layui-btn-warm" lay-event="setInStock">改为有货</a> |
| | | <a class="layui-btn layui-btn-danger layui-btn-xs btn-edit" lay-event="del">删除</a> |
| | | </script> |
| | | |