From 67f8506fae165dc7a292355a638f9c46de70f10f Mon Sep 17 00:00:00 2001 From: "HUOJIN\\92525" Date: Tue, 10 Feb 2026 17:03:38 +0800 Subject: [PATCH] no message --- .../agvTask/controller/AgvTaskController.java | 5 - .../service/processor/ScanTrayProcessor.java | 2 +- .../shipping/controller/PickController.java | 20 ++ .../service/processor/AllocateProcessor.java | 173 +++++++++++++++--- .../modules/shipping/vo/InventoryScore.java | 17 +- 5 files changed, 176 insertions(+), 41 deletions(-) diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/agvTask/controller/AgvTaskController.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/agvTask/controller/AgvTaskController.java index 4f02d04..4ab6649 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/agvTask/controller/AgvTaskController.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/agvTask/controller/AgvTaskController.java @@ -3,20 +3,15 @@ package org.cpte.modules.agvTask.controller; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.commons.lang3.StringUtils; import org.apache.poi.ss.usermodel.Workbook; import org.cpte.modules.agvTask.mapper.AgvTaskMapper; import org.cpte.modules.agvTask.vo.ExportAgvTask; import org.cpte.modules.constant.enums.AgvStatusEnum; import org.cpte.modules.constant.enums.BusinessTypeEnum; -import org.cpte.modules.constant.enums.InventoryStatusEnum; -import org.cpte.modules.inventory.vo.ExportInventory; import org.jeecg.common.api.vo.Result; import org.jeecg.common.system.query.QueryGenerator; import org.cpte.modules.agvTask.entity.AgvTask; diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/service/processor/ScanTrayProcessor.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/service/processor/ScanTrayProcessor.java index 4338c68..0a27fa5 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/service/processor/ScanTrayProcessor.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/service/processor/ScanTrayProcessor.java @@ -433,7 +433,7 @@ public class ScanTrayProcessor { // double clusterPotentialScore = calculateClusterPotentialScore(point, itemKeys) * 0.2; // 权重20% totalScore = distanceScore + channelDepthScore + channelScore + balanceScore; - log.info("【{}】库位总分:{} - 距离评分: {} - 通道深度策略评分: {} - 通道类型评分: {} - 均衡评分: {} - 物料聚集评分: {}", point.getPointCode(), totalScore, distanceScore, channelDepthScore, channelScore, balanceScore, 0); + //log.info("【{}】库位总分:{} - 距离评分: {} - 通道深度策略评分: {} - 通道类型评分: {} - 均衡评分: {} - 物料聚集评分: {}", point.getPointCode(), totalScore, distanceScore, channelDepthScore, channelScore, balanceScore, 0); return new PointScore(point, totalScore); } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/controller/PickController.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/controller/PickController.java index c7148de..5730c89 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/controller/PickController.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/controller/PickController.java @@ -388,4 +388,24 @@ public class PickController { return Result.error("操作异常:" + e.getMessage()); } } + + @AutoLog(value = "批量分配") + @Operation(summary = "批量分配") + @RequiresPermissions("shipping:data_pick:allocatePick") + @GetMapping(value = "/allocatePickDetail") + public Result allocatePickDetail(@RequestParam(name = "id", required = true) String id) { + try { + PickDetail pickDetail = pickDetailService.getById(Long.parseLong(id)); + boolean flag = pickService.allocatePickDetail(pickDetail); + if (flag) { + String result = "分配成功"; + return Result.OK(result); + } else { + String result = "分配失败"; + return Result.error(result); + } + } catch (Exception e) { + return Result.error("分配异常:" + e.getMessage()); + } + } } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/processor/AllocateProcessor.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/processor/AllocateProcessor.java index e31742e..56f2f7f 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/processor/AllocateProcessor.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/processor/AllocateProcessor.java @@ -37,6 +37,7 @@ 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.stream.Collectors; @@ -211,11 +212,11 @@ public class AllocateProcessor { data.setItemMap(itemMap); //筛选查询库存的条件(非空去重)外部仓库、项目号、任务号、批次、外部库存状态 - List whCodeList = pickMap.values().stream().map(Pick::getWhCode).distinct().toList(); - List projectList = pickDetails.stream().map(PickDetail::getProject).distinct().toList(); - List taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).distinct().toList(); - List propC1List = pickDetails.stream().map(PickDetail::getPropC1).distinct().toList(); - List propC3List = pickDetails.stream().map(PickDetail::getPropC3).distinct().toList(); + List whCodeList = pickMap.values().stream().map(Pick::getWhCode).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList(); + List projectList = pickDetails.stream().map(PickDetail::getProject).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList(); + List taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList(); + List propC1List = pickDetails.stream().map(PickDetail::getPropC1).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList(); + List propC3List = pickDetails.stream().map(PickDetail::getPropC3).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList(); List itemKeys = itemKeyMapper.queryItemKeys(itemIds, whCodeList, projectList, taskNoList, propC1List, propC3List); //出库明细指定了容器 if (CollectionUtils.isNotEmpty(containerIds)) { @@ -305,11 +306,11 @@ public class AllocateProcessor { data.setItemMap(itemMap); //筛选查询库存的条件(非空去重)外部仓库、项目号、任务号、批次、外部库存状态 - List whCodeList = pickMap.values().stream().map(Pick::getWhCode).distinct().toList(); - List projectList = pickDetails.stream().map(PickDetail::getProject).distinct().toList(); - List taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).distinct().toList(); - List propC1List = pickDetails.stream().map(PickDetail::getPropC1).distinct().toList(); - List propC3List = pickDetails.stream().map(PickDetail::getPropC3).distinct().toList(); + List whCodeList = pickMap.values().stream().map(Pick::getWhCode).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList(); + List projectList = pickDetails.stream().map(PickDetail::getProject).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList(); + List taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList(); + List propC1List = pickDetails.stream().map(PickDetail::getPropC1).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList(); + List propC3List = pickDetails.stream().map(PickDetail::getPropC3).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList(); List itemKeys = itemKeyMapper.queryItemKeys(itemIds, whCodeList, projectList, taskNoList, propC1List, propC3List); //出库明细指定了容器 if (CollectionUtils.isNotEmpty(containerIds)) { @@ -522,7 +523,8 @@ public class AllocateProcessor { AllocationData data, BigDecimal totalUnAllocatedQty, Set errorMsgSet) { // 智能排序库存 - List scoredInventories = scoreInventories(matchedInventories); + BigDecimal orderQty = pickDetail.getOrderQty(); + List scoredInventories = scoreInventories(matchedInventories, orderQty); //指定容器出库 if (pickDetail.getStockId() != null) { @@ -583,29 +585,148 @@ public class AllocateProcessor { * @param matchedInventories 库存集合 * @return List */ - private List scoreInventories(List matchedInventories) { + private List scoreInventories(List matchedInventories, BigDecimal orderQty) { + if (CollectionUtils.isEmpty(matchedInventories)) { + return new ArrayList<>(); + } // 批量查询库位 List pointIds = matchedInventories.stream() .map(Inventory::getPointId) .toList(); Map pointMap = pointService.queryByPointIdsToMap(pointIds); - //获取出库口的库位 - // List outPoints = pointService.queryPoints(null, null, AreaTypeEnum.CK_DOCK.getValue()); //获取优化后的库存 return matchedInventories.stream() .map(inventory -> { Point currPoint = pointMap.get(inventory.getPointId()); - return calculateMoveCount(inventory, currPoint); + // 计算可用数量 + BigDecimal availableQty = BigDecimalUtil.subtract(inventory.getQuantity(), inventory.getQueuedQty(), 0); + // 计算匹配度得分 + double matchScore = calculateMatchScore(availableQty, orderQty); + boolean perfectMatch = availableQty.compareTo(orderQty) == 0; + + // 计算移位得分 + double moveScore = calculateMoveScore(currPoint); + List movePoints = calculateMovePoints(currPoint); + + // 计算综合得分(权重分配) + double totalScore = calculateTotalScore(matchScore, moveScore, perfectMatch); + + log.info("库存评分详情 - 库位: {}, 匹配度: {}, 移位: {}, 总分: {}, 完全匹配: {}", + currPoint.getPointCode(), matchScore, moveScore, totalScore, perfectMatch); + + return new InventoryScore(inventory, inventory.getStockId(), + totalScore, matchScore, moveScore, + movePoints, currPoint.getPointCode(), availableQty, perfectMatch); }) - //按分数倒序排序、移动次数升序排序 - .sorted(Comparator.comparing(InventoryScore::getScore).reversed() - .thenComparing(score -> score.getMovePoints().size()) - ) + .sorted(Comparator + // 1. 总分高优先 + .comparing(InventoryScore::getTotalScore).reversed() + // 2. 完全匹配优先(这是关键业务逻辑) + .thenComparing(InventoryScore::isPerfectMatch).reversed() + // 3. 匹配度高的优先 + .thenComparing(InventoryScore::getMatchScore, Comparator.reverseOrder()) + // 4. 移位次数少的优先 + .thenComparingInt(score -> score.getMovePoints().size())) .toList(); } + /** + * 计算匹配度得分 + * 完全匹配:100分 + * 库存足够:60-90分(按比例) + * 库存不足:0-60分(按比例) + */ + private double calculateMatchScore(BigDecimal availableQty, BigDecimal orderQty) { + if (availableQty.compareTo(BigDecimal.ZERO) <= 0) { + return 0; + } + + // 完全匹配:100分 + if (availableQty.compareTo(orderQty) == 0) { + return 100.0; + } + + // 库存大于需求 + if (availableQty.compareTo(orderQty) > 0) { + // 计算比例:需求/库存 + BigDecimal ratio = orderQty.divide(availableQty, 4, RoundingMode.HALF_UP); + // 80-90分之间,库存越多分数越低 + return 80.0 + (20.0 * ratio.doubleValue()); + } + + // 库存小于需求 + BigDecimal ratio = availableQty.divide(orderQty, 4, RoundingMode.HALF_UP); + // 0-60分之间,库存越接近需求分数越高 + return 60.0 * ratio.doubleValue(); + } + + + /** + * 计算移位得分(基于您的原有逻辑) + */ + private double calculateMoveScore(Point currPoint) { + // 移位库位 + List movePoints = calculateMovePoints(currPoint); + + // 计算移位次数得分 + if (movePoints.isEmpty()) { + return 100.0; // 无需移位,最高分 + } + + int moveCount = movePoints.size(); + + // 移位次数越少得分越高,使用指数衰减 + return 100.0 * Math.exp(-0.3 * moveCount); + } + + /** + * 计算移位库位 + */ + private List calculateMovePoints(Point currPoint) { + List movePoints; + + //当前巷道的库位数 + List points = pointMapper.findByColAndLayer(currPoint.getColNum(), currPoint.getLayerNum()); + + // 目标库位的深度位转换为索引 + int targetIndex = Integer.parseInt(currPoint.getRowNum()) - 1; + + //双通道 + if (currPoint.getIzDoubleLane().equals(1)) { + // 计算左侧占用数 + List leftPoints = calculateUsedPoints(points, 0, targetIndex); + // 计算右侧占用数 + List rightPoints = calculateUsedPoints(points, targetIndex + 1, points.size()); + //取两个集合中,元素最少的那个集合 + movePoints = leftPoints.size() < rightPoints.size() ? leftPoints : rightPoints; + } else { + movePoints = calculateUsedPoints(points, 0, targetIndex); + } + + return movePoints; + } + + /** + * 计算综合得分 + * 权重配置:匹配度40%,移位30% + * 完全匹配额外奖励20分 + */ + private double calculateTotalScore(double matchScore, + double moveScore, boolean perfectMatch) { + // 基础权重得分 + double totalScore = (matchScore * 0.7) + (moveScore * 0.3); + + // 如果完全匹配,给予额外奖励 + if (perfectMatch) { + totalScore += 20.0; + } + + return totalScore; + } + + /** * 计算位移次数核心算法 * @@ -621,11 +742,6 @@ public class AllocateProcessor { String currOutPointCode = currPoint.getPointCode(); - // 计算距离分数(权重30%) - //Point bestPoint = getBestOutboundPoint(currPoint, outPoints); - //double distanceScore = calculateClusterDistanceCost(currPoint, bestPoint) * 0.3; - - double distanceScore = 0; //当前巷道的库位数 List points = pointMapper.findByColAndLayer(currPoint.getColNum(), currPoint.getLayerNum()); @@ -649,13 +765,10 @@ public class AllocateProcessor { moveScore = (100.0 / (movePoints.size() + 1)) * 0.7; } - //库位得分 = 距离得分 + 移动得分 - double totalScore = distanceScore + moveScore; + //库位得分 = 移动得分 + double totalScore = moveScore; - log.info("【{}】库位距离评分: {}-移位评分: {}-总得分: {}-移位次数: {}", - currPoint.getPointCode(), distanceScore, moveScore, totalScore, movePoints.size()); - - return new InventoryScore(inventory, inventory.getStockId(), totalScore, movePoints, currOutPointCode); + return new InventoryScore(inventory, inventory.getStockId(), totalScore, 0, 0, movePoints, currOutPointCode, BigDecimal.ZERO, false); } /** diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/InventoryScore.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/InventoryScore.java index bce5aa8..bb8e1f5 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/InventoryScore.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/InventoryScore.java @@ -5,6 +5,7 @@ import lombok.Data; import org.cpte.modules.base.entity.Point; import org.cpte.modules.inventory.entity.Inventory; +import java.math.BigDecimal; import java.util.List; import java.util.Map; @@ -15,12 +16,18 @@ public class InventoryScore { private Inventory inventory; //容器 private Long stockId; - // 分数 - private double score; - //最佳出库口 - //private Point outPoint; + // 综合得分 + private double totalScore; + // 匹配度得分 + private double matchScore; + // 移位得分 + private double moveScore; //移位的库位 private List movePoints; - // + //当前出库库位 private String currOutPointCode; + // 可用数量 + private BigDecimal availableQty; + // 是否完全匹配 + private boolean perfectMatch; }