| | |
| | | import com.zy.asrs.entity.*; |
| | | import com.zy.asrs.entity.param.*; |
| | | import com.zy.asrs.enums.LocStsType; |
| | | import com.zy.asrs.enums.TaskIOType; |
| | | import com.zy.asrs.mapper.BasDevpMapper; |
| | | import com.zy.asrs.mapper.BasStationMapper; |
| | | import com.zy.asrs.mapper.LocMastMapper; |
| | | import com.zy.asrs.mapper.ManLocDetlMapper; |
| | | import com.zy.asrs.service.*; |
| | | import com.zy.asrs.task.core.ReturnT; |
| | | import com.zy.asrs.utils.MatUtils; |
| | | import com.zy.common.constant.ApiInterfaceConstant; |
| | | import com.zy.common.constant.MesConstant; |
| | | import com.zy.common.entity.Parameter; |
| | | import com.zy.common.model.DetlDto; |
| | | import com.zy.common.model.MesCombParam; |
| | | import com.zy.common.model.enums.WorkNoType; |
| | | import com.zy.common.properties.AgvProperties; |
| | | import com.zy.common.service.CommonService; |
| | | import com.zy.common.utils.HttpHandler; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.poi.ss.formula.functions.T; |
| | | import org.springframework.beans.BeanUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.*; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.atomic.AtomicInteger; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | |
| | | private BasStationService basStationService; |
| | | @Autowired |
| | | private BasContainerService basContainerService; |
| | | |
| | | @Resource |
| | | private BasStationMapper basStationMapper; |
| | | |
| | | @Resource |
| | | private BasDevpMapper basDevpMapper; |
| | | |
| | | @Resource |
| | | private AgvProperties agvProperties; |
| | | |
| | | /** |
| | | * 站点轮询计数器,用于平均分配站点 |
| | | * Key: 站点组标识(如 "east" 或 "west"),Value: 当前轮询索引 |
| | | */ |
| | | private final Map<String, AtomicInteger> siteRoundRobinCounters = new ConcurrentHashMap<>(); |
| | | |
| | | @Override |
| | | public R inLocCallAgv(CallAgvParam param,Long userId) { |
| | | int type = param.getType(); |
| | | String sourceSite = param.getSourceSite(); |
| | | String barcode = param.getBarcode(); |
| | | |
| | | // 检查托盘码和暂存位编码是否相同 |
| | | if (barcode != null && sourceSite != null && barcode.trim().equals(sourceSite.trim())) { |
| | | throw new CoolException("托盘码和暂存位编码不能相同"); |
| | | } |
| | | |
| | | int ioType; |
| | | // 查询源站点(库位)信息,但不检查是否存在,允许下单成功 |
| | | // 站点不存在的检查将在定时任务(AgvHandler.callAgv)中进行 |
| | | LocCache locCache = locCacheService.selectOne(new EntityWrapper<LocCache>().eq("loc_no", sourceSite)); |
| | | switch (type) { |
| | | case 1: |
| | | // 判断有没有组托 |
| | | int count = waitPakinService.selectCount(new EntityWrapper<WaitPakin>().eq("zpallet", barcode)); |
| | | if (count == 0) { |
| | | throw new CoolException("条码未组托:" + barcode); |
| | | } |
| | | ioType = 1; // AGV容器入库(实托入库) |
| | | |
| | | // 如果库位存在,更新状态为入库预约;不存在则跳过,由定时任务处理 |
| | | if (locCache != null) { |
| | | locCache.setLocSts(LocStsType.LOC_STS_TYPE_S.type); // S.入库预约 |
| | | locCacheService.updateById(locCache); |
| | | } |
| | | break; |
| | | case 2: |
| | | // 判断是拣选回库托盘 |
| | | WrkMast wrkMast = wrkMastService.selectOne(new EntityWrapper<WrkMast>().eq("barcode", barcode)); |
| | | if (wrkMast == null) { |
| | | throw new CoolException("条码不存在:" + barcode); |
| | | } |
| | | if (wrkMast.getIoType() != 103 && wrkMast.getIoType() != 107) { |
| | | throw new CoolException("条码不需要回库:" + barcode); |
| | | } |
| | | ioType = wrkMast.getIoType() - 50; // 103->53(拣料入库), 107->57(盘点入库) |
| | | |
| | | // 如果库位存在,更新状态为入库预约;不存在则跳过,由定时任务处理 |
| | | if (locCache != null) { |
| | | locCache.setLocSts(LocStsType.LOC_STS_TYPE_S.type); // S.入库预约(容器回库是入库操作) |
| | | locCacheService.updateById(locCache); |
| | | } |
| | | break; |
| | | case 3: |
| | | // 判断是否为空托入库:检查条码在wms中不存在,确认为空托盘 |
| | | log.info("开始判断是否为空托入库,条码:{}", barcode); |
| | | |
| | | // 检查是否已组托 |
| | | int waitPakInCount = waitPakinService.selectCount(new EntityWrapper<WaitPakin>().eq("zpallet", barcode)); |
| | | if (waitPakInCount != 0) { |
| | | log.warn("条码组托档已存在,不是空托盘:{}", barcode); |
| | | throw new CoolException("条码组托档已存在:" + barcode); |
| | | } |
| | | |
| | | // 检查是否有任务 |
| | | int wrkMastCount = wrkMastService.selectCount(new EntityWrapper<WrkMast>().eq("barcode", barcode)); |
| | | if (wrkMastCount != 0) { |
| | | log.warn("条码任务档已存在,不是空托盘:{}", barcode); |
| | | throw new CoolException("条码任务档已存在:" + barcode); |
| | | } |
| | | |
| | | // 检查是否有库存 |
| | | int locDetlCount = locDetlService.selectCount(new EntityWrapper<LocDetl>().eq("zpallet", barcode)); |
| | | if (locDetlCount != 0) { |
| | | log.warn("条码库存已存在,不是空托盘:{}", barcode); |
| | | throw new CoolException("条码库存已存在:" + barcode); |
| | | } |
| | | |
| | | // 通过所有检查,确认为空托盘,设置为空托入库 |
| | | ioType = 10; |
| | | log.info("确认为空托盘,设置为空托入库,条码:{},ioType:{}", barcode, ioType); |
| | | break; |
| | | default: |
| | | throw new CoolException("入库类型错误,type:" + type); |
| | | } |
| | | // 条码存在agv任务 |
| | | int count = taskService.selectCount(new EntityWrapper<Task>().eq("barcode", barcode).eq("is_deleted", 0)); |
| | | if (count > 0) { |
| | | throw new CoolException(barcode+ ":条码存在agv搬运任务!"); |
| | | } |
| | | |
| | | // 根据whs_type确定机器人组(站点分配完全由定时任务处理) |
| | | // 如果库位不存在,使用默认逻辑(根据type判断) |
| | | Long whsType = locCache != null ? locCache.getWhsType() : null; |
| | | String robotGroup; |
| | | |
| | | if (whsType != null && whsType.equals(agvProperties.getWhsTypeMapping().getInboundArea())) { |
| | | // whs_type = 1: 入库区,使用Group-001 |
| | | robotGroup = agvProperties.getRobotGroupEast(); |
| | | log.info("库位whs_type={},使用入库区配置({})", whsType, robotGroup); |
| | | } else if (whsType != null && whsType.equals(agvProperties.getWhsTypeMapping().getCacheArea())) { |
| | | // whs_type = 2: 缓存区,使用Group-002 |
| | | robotGroup = agvProperties.getRobotGroupWest(); |
| | | log.info("库位whs_type={},使用缓存区配置({})", whsType, robotGroup); |
| | | } else { |
| | | // whs_type为空或其他值,根据type判断(兼容旧逻辑) |
| | | // 如果库位不存在,也使用此逻辑 |
| | | if (type == 1) { |
| | | robotGroup = agvProperties.getRobotGroupEast(); |
| | | } else { |
| | | robotGroup = agvProperties.getRobotGroupWest(); |
| | | } |
| | | if (locCache == null) { |
| | | log.warn("源站点(库位){}不存在,使用type={}的默认逻辑,机器人组:{},站点分配将在定时任务中进行", sourceSite, type, robotGroup); |
| | | } else { |
| | | log.warn("库位whs_type={}未配置或不在映射范围内,使用type={}的默认逻辑,机器人组:{}", whsType, type, robotGroup); |
| | | } |
| | | } |
| | | |
| | | // 站点分配完全由定时任务处理,此处不分配站点,只创建任务 |
| | | log.info("创建AGV任务,站点分配将在定时任务中处理,机器人组:{}", robotGroup); |
| | | |
| | | |
| | | // 获取工作号 |
| | | int workNo = commonService.getWorkNo(WorkNoType.PICK.type); |
| | | // 保存工作档 |
| | | Task task = new Task(); |
| | | Date now = new Date(); |
| | | task.setWrkNo(workNo) |
| | | .setIoTime(now) |
| | | .setWrkSts(7L) // 工作状态:11.生成出库ID |
| | | .setIoType(ioType) // 入出库状态: 1.入库 |
| | | .setTaskType("agv") |
| | | .setIoPri(10D) |
| | | .setStaNo(null) // 站点分配完全由定时任务处理 |
| | | .setSourceStaNo(sourceSite) // 设置源站点 |
| | | .setInvWh(robotGroup) // 根据whs_type设置机器人组 |
| | | .setFullPlt(ioType == 10 ? "N" : "Y")// 空托入库(ioType=10)设置为N,其他入库设置为Y(满板) |
| | | .setPicking("N") // 拣料 |
| | | .setExitMk("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) |
| | | .setModiTime(now); |
| | | if (!taskService.insert(task)) { |
| | | throw new CoolException("保存工作档失败"); |
| | | } |
| | | |
| | | // 如果库位存在,根据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任务生成成功!"); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional |
| | |
| | | if (Cools.isEmpty(mat)) { |
| | | throw new CoolException(detlDto.getMatnr() + "商品档案不存在"); |
| | | } |
| | | if (mat.getUpQty().compareTo(detlDto.getAnfme()) < 0) { |
| | | throw new CoolException("物料:" + detlDto.getMatnr() + "单次最大组托上限为:" + mat.getUpQty()); |
| | | } |
| | | // if (mat.getUpQty().compareTo(detlDto.getAnfme()) < 0) { |
| | | // throw new CoolException("物料:" + detlDto.getMatnr() + "单次最大组托上限为:" + mat.getUpQty()); |
| | | // } |
| | | WaitPakin waitPakin = new WaitPakin(); |
| | | BeanUtils.copyProperties(mat, waitPakin); |
| | | waitPakin.setBatch(detlDto.getBatch()); |
| | |
| | | param.getCombMats().forEach(elem -> { |
| | | OrderPakin order = orderPakinService.selectByNo(elem.getOrderNo()); |
| | | if (Cools.isEmpty(order) || order.getSettle() > 2) { |
| | | throw new CoolException("单据编号已过期"); |
| | | throw new CoolException("单据正在作业中"); |
| | | } |
| | | // 订单明细数量校验 |
| | | OrderDetlPakin detls = orderDetlPakinService.selectOne(new EntityWrapper<OrderDetlPakin>() |
| | |
| | | } |
| | | }); |
| | | |
| | | BasContainer container = basContainerService.selectOne(new EntityWrapper<BasContainer>().eq("barcode", param.getBarcode())); |
| | | if (Objects.isNull(container)) { |
| | | throw new CoolException("数据错误:容器码不存在!!"); |
| | | } |
| | | if (container.getMixMax() < detlDtos.size()) { |
| | | throw new CoolException("超出容器最大混装数量,当前容器最大数量为:" + container.getMixMax() + "!!"); |
| | | } |
| | | Set<String> matnrs = detlDtos.stream().map(DetlDto::getMatnr).collect(Collectors.toSet()); |
| | | List<Mat> mats = matService.selectList(new EntityWrapper<Mat>().in("matnr", matnrs)); |
| | | Set<Long> tagIds = mats.stream().map(Mat::getTagId).collect(Collectors.toSet()); |
| | | if (tagIds.size() > 1) { |
| | | throw new CoolException("组托物料类型不一致,只有相同的物料分类才可以组托!!"); |
| | | } |
| | | //还可以放入多少种物料 |
| | | Integer suplus = container.getMixMax(); |
| | | // BasContainer container = basContainerService.selectOne(new EntityWrapper<BasContainer>().eq("barcode", param.getBarcode())); |
| | | // if (Objects.isNull(container)) { |
| | | // throw new CoolException("数据错误:容器码不存在!!"); |
| | | // } |
| | | // if (container.getMixMax() < detlDtos.size()) { |
| | | // throw new CoolException("超出容器最大混装数量,当前容器最大数量为:" + container.getMixMax() + "!!"); |
| | | // } |
| | | // Set<String> matnrs = detlDtos.stream().map(DetlDto::getMatnr).collect(Collectors.toSet()); |
| | | // List<Mat> mats = matService.selectList(new EntityWrapper<Mat>().in("matnr", matnrs)); |
| | | // Set<Long> tagIds = mats.stream().map(Mat::getTagId).collect(Collectors.toSet()); |
| | | // if (tagIds.size() > 1) { |
| | | // throw new CoolException("组托物料类型不一致,只有相同的物料分类才可以组托!!"); |
| | | // } |
| | | // //还可以放入多少种物料 |
| | | // Integer suplus = container.getMixMax(); |
| | | for (DetlDto detlDto : detlDtos) { |
| | | Mat mat = matService.selectByMatnr(detlDto.getMatnr()); |
| | | if (Cools.isEmpty(mat)) { |
| | | throw new CoolException(detlDto.getMatnr() + "商品档案不存在"); |
| | | } |
| | | //最多可放数量 |
| | | Double singleMax = mat.getUpQty() * suplus; |
| | | if (singleMax.compareTo(detlDto.getAnfme()) < 0) { |
| | | throw new CoolException("物料:" + detlDto.getMatnr() + "单次组托上限为:" + mat.getUpQty() + ",当前总量超出托盘装载上限!!"); |
| | | } |
| | | BigDecimal decimal = new BigDecimal(detlDto.getAnfme() / mat.getUpQty()); |
| | | //当前物料需要占用料箱格数 |
| | | Integer curr = decimal.setScale(0, RoundingMode.CEILING).intValue(); |
| | | suplus = suplus - curr; |
| | | if (suplus < 0) { |
| | | throw new CoolException("物料:" + detlDto.getMatnr() + ", 超出当前托盘装载上限!!"); |
| | | } |
| | | // //最多可放数量 |
| | | // Double singleMax = mat.getUpQty() * suplus; |
| | | // if (singleMax.compareTo(detlDto.getAnfme()) < 0) { |
| | | // throw new CoolException("物料:" + detlDto.getMatnr() + "单次组托上限为:" + mat.getUpQty() + ",当前总量超出托盘装载上限!!"); |
| | | // } |
| | | // BigDecimal decimal = new BigDecimal(detlDto.getAnfme() / mat.getUpQty()); |
| | | // //当前物料需要占用料箱格数 |
| | | // Integer curr = decimal.setScale(0, RoundingMode.CEILING).intValue(); |
| | | // suplus = suplus - curr; |
| | | // if (suplus < 0) { |
| | | // throw new CoolException("物料:" + detlDto.getMatnr() + ", 超出当前托盘装载上限!!"); |
| | | // } |
| | | |
| | | WaitPakin waitPakin = new WaitPakin(); |
| | | BeanUtils.copyProperties(mat, waitPakin); |
| | |
| | | boolean success = false; |
| | | try { |
| | | response = new HttpHandler.Builder() |
| | | .setUri(MesConstant.URL) |
| | | .setUri(MesConstant.URI) |
| | | .setPath(MesConstant.PACK_DOWN_URL) |
| | | .setJson(JSON.toJSONString(mesCombParam)) |
| | | .build() |
| | |
| | | if (jsonObject.getInteger("code").equals(200)) { |
| | | success = true; |
| | | } else if (jsonObject.getInteger("code").equals(500)) { |
| | | log.error("请求接口失败!!!url:{};request:{};response:{}", MesConstant.URL + MesConstant.PACK_DOWN_URL, JSON.toJSONString(mesCombParam), response); |
| | | log.error("请求接口失败!!!url:{};request:{};response:{}", MesConstant.URI + MesConstant.PACK_DOWN_URL, JSON.toJSONString(mesCombParam), response); |
| | | throw new CoolException(jsonObject.getString("msg")); |
| | | } else { |
| | | log.error("请求接口失败!!!url:{};request:{};response:{}", MesConstant.URL + MesConstant.PACK_DOWN_URL, JSON.toJSONString(mesCombParam), response); |
| | | log.error("请求接口失败!!!url:{};request:{};response:{}", MesConstant.URI + MesConstant.PACK_DOWN_URL, JSON.toJSONString(mesCombParam), response); |
| | | throw new CoolException("上报mes系统失败"); |
| | | } |
| | | } catch (Exception e) { |
| | |
| | | // 保存接口日志 |
| | | apiLogService.save( |
| | | "打包下线帮托上报", |
| | | MesConstant.URL + MesConstant.PACK_DOWN_URL, |
| | | MesConstant.URI + MesConstant.PACK_DOWN_URL, |
| | | null, |
| | | "127.0.0.1", |
| | | JSON.toJSONString(mesCombParam), |