no message

main
HUOJIN\92525 2026-02-10 17:03:38 +08:00
parent 9719bcf427
commit 67f8506fae
5 changed files with 176 additions and 41 deletions

View File

@ -3,20 +3,15 @@ package org.cpte.modules.agvTask.controller;
import java.io.IOException; import java.io.IOException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
import org.cpte.modules.agvTask.mapper.AgvTaskMapper; import org.cpte.modules.agvTask.mapper.AgvTaskMapper;
import org.cpte.modules.agvTask.vo.ExportAgvTask; import org.cpte.modules.agvTask.vo.ExportAgvTask;
import org.cpte.modules.constant.enums.AgvStatusEnum; import org.cpte.modules.constant.enums.AgvStatusEnum;
import org.cpte.modules.constant.enums.BusinessTypeEnum; 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.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.common.system.query.QueryGenerator;
import org.cpte.modules.agvTask.entity.AgvTask; import org.cpte.modules.agvTask.entity.AgvTask;

View File

@ -433,7 +433,7 @@ public class ScanTrayProcessor {
// double clusterPotentialScore = calculateClusterPotentialScore(point, itemKeys) * 0.2; // 权重20% // double clusterPotentialScore = calculateClusterPotentialScore(point, itemKeys) * 0.2; // 权重20%
totalScore = distanceScore + channelDepthScore + channelScore + balanceScore; 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); return new PointScore(point, totalScore);
} }

View File

@ -388,4 +388,24 @@ public class PickController {
return Result.error("操作异常:" + e.getMessage()); return Result.error("操作异常:" + e.getMessage());
} }
} }
@AutoLog(value = "批量分配")
@Operation(summary = "批量分配")
@RequiresPermissions("shipping:data_pick:allocatePick")
@GetMapping(value = "/allocatePickDetail")
public Result<String> 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());
}
}
} }

View File

@ -37,6 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -211,11 +212,11 @@ public class AllocateProcessor {
data.setItemMap(itemMap); data.setItemMap(itemMap);
//筛选查询库存的条件(非空去重)外部仓库、项目号、任务号、批次、外部库存状态 //筛选查询库存的条件(非空去重)外部仓库、项目号、任务号、批次、外部库存状态
List<String> whCodeList = pickMap.values().stream().map(Pick::getWhCode).distinct().toList(); List<String> whCodeList = pickMap.values().stream().map(Pick::getWhCode).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList();
List<String> projectList = pickDetails.stream().map(PickDetail::getProject).distinct().toList(); List<String> projectList = pickDetails.stream().map(PickDetail::getProject).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList();
List<String> taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).distinct().toList(); List<String> taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList();
List<String> propC1List = pickDetails.stream().map(PickDetail::getPropC1).distinct().toList(); List<String> propC1List = pickDetails.stream().map(PickDetail::getPropC1).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList();
List<String> propC3List = pickDetails.stream().map(PickDetail::getPropC3).distinct().toList(); List<String> propC3List = pickDetails.stream().map(PickDetail::getPropC3).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList();
List<ItemKey> itemKeys = itemKeyMapper.queryItemKeys(itemIds, whCodeList, projectList, taskNoList, propC1List, propC3List); List<ItemKey> itemKeys = itemKeyMapper.queryItemKeys(itemIds, whCodeList, projectList, taskNoList, propC1List, propC3List);
//出库明细指定了容器 //出库明细指定了容器
if (CollectionUtils.isNotEmpty(containerIds)) { if (CollectionUtils.isNotEmpty(containerIds)) {
@ -305,11 +306,11 @@ public class AllocateProcessor {
data.setItemMap(itemMap); data.setItemMap(itemMap);
//筛选查询库存的条件(非空去重)外部仓库、项目号、任务号、批次、外部库存状态 //筛选查询库存的条件(非空去重)外部仓库、项目号、任务号、批次、外部库存状态
List<String> whCodeList = pickMap.values().stream().map(Pick::getWhCode).distinct().toList(); List<String> whCodeList = pickMap.values().stream().map(Pick::getWhCode).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList();
List<String> projectList = pickDetails.stream().map(PickDetail::getProject).distinct().toList(); List<String> projectList = pickDetails.stream().map(PickDetail::getProject).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList();
List<String> taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).distinct().toList(); List<String> taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList();
List<String> propC1List = pickDetails.stream().map(PickDetail::getPropC1).distinct().toList(); List<String> propC1List = pickDetails.stream().map(PickDetail::getPropC1).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList();
List<String> propC3List = pickDetails.stream().map(PickDetail::getPropC3).distinct().toList(); List<String> propC3List = pickDetails.stream().map(PickDetail::getPropC3).distinct().filter(s -> s != null && !s.trim().isEmpty()).toList();
List<ItemKey> itemKeys = itemKeyMapper.queryItemKeys(itemIds, whCodeList, projectList, taskNoList, propC1List, propC3List); List<ItemKey> itemKeys = itemKeyMapper.queryItemKeys(itemIds, whCodeList, projectList, taskNoList, propC1List, propC3List);
//出库明细指定了容器 //出库明细指定了容器
if (CollectionUtils.isNotEmpty(containerIds)) { if (CollectionUtils.isNotEmpty(containerIds)) {
@ -522,7 +523,8 @@ public class AllocateProcessor {
AllocationData data, BigDecimal totalUnAllocatedQty, Set<String> errorMsgSet) { AllocationData data, BigDecimal totalUnAllocatedQty, Set<String> errorMsgSet) {
// 智能排序库存 // 智能排序库存
List<InventoryScore> scoredInventories = scoreInventories(matchedInventories); BigDecimal orderQty = pickDetail.getOrderQty();
List<InventoryScore> scoredInventories = scoreInventories(matchedInventories, orderQty);
//指定容器出库 //指定容器出库
if (pickDetail.getStockId() != null) { if (pickDetail.getStockId() != null) {
@ -583,29 +585,148 @@ public class AllocateProcessor {
* @param matchedInventories * @param matchedInventories
* @return List<InventoryScore> * @return List<InventoryScore>
*/ */
private List<InventoryScore> scoreInventories(List<Inventory> matchedInventories) { private List<InventoryScore> scoreInventories(List<Inventory> matchedInventories, BigDecimal orderQty) {
if (CollectionUtils.isEmpty(matchedInventories)) {
return new ArrayList<>();
}
// 批量查询库位 // 批量查询库位
List<Long> pointIds = matchedInventories.stream() List<Long> pointIds = matchedInventories.stream()
.map(Inventory::getPointId) .map(Inventory::getPointId)
.toList(); .toList();
Map<Long, Point> pointMap = pointService.queryByPointIdsToMap(pointIds); Map<Long, Point> pointMap = pointService.queryByPointIdsToMap(pointIds);
//获取出库口的库位
// List<Point> outPoints = pointService.queryPoints(null, null, AreaTypeEnum.CK_DOCK.getValue());
//获取优化后的库存 //获取优化后的库存
return matchedInventories.stream() return matchedInventories.stream()
.map(inventory -> { .map(inventory -> {
Point currPoint = pointMap.get(inventory.getPointId()); 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<Point> 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
.sorted(Comparator.comparing(InventoryScore::getScore).reversed() // 1. 总分高优先
.thenComparing(score -> score.getMovePoints().size()) .comparing(InventoryScore::getTotalScore).reversed()
) // 2. 完全匹配优先(这是关键业务逻辑)
.thenComparing(InventoryScore::isPerfectMatch).reversed()
// 3. 匹配度高的优先
.thenComparing(InventoryScore::getMatchScore, Comparator.reverseOrder())
// 4. 移位次数少的优先
.thenComparingInt(score -> score.getMovePoints().size()))
.toList(); .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<Point> movePoints = calculateMovePoints(currPoint);
// 计算移位次数得分
if (movePoints.isEmpty()) {
return 100.0; // 无需移位,最高分
}
int moveCount = movePoints.size();
// 移位次数越少得分越高,使用指数衰减
return 100.0 * Math.exp(-0.3 * moveCount);
}
/**
*
*/
private List<Point> calculateMovePoints(Point currPoint) {
List<Point> movePoints;
//当前巷道的库位数
List<Point> points = pointMapper.findByColAndLayer(currPoint.getColNum(), currPoint.getLayerNum());
// 目标库位的深度位转换为索引
int targetIndex = Integer.parseInt(currPoint.getRowNum()) - 1;
//双通道
if (currPoint.getIzDoubleLane().equals(1)) {
// 计算左侧占用数
List<Point> leftPoints = calculateUsedPoints(points, 0, targetIndex);
// 计算右侧占用数
List<Point> 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(); String currOutPointCode = currPoint.getPointCode();
// 计算距离分数权重30%
//Point bestPoint = getBestOutboundPoint(currPoint, outPoints);
//double distanceScore = calculateClusterDistanceCost(currPoint, bestPoint) * 0.3;
double distanceScore = 0;
//当前巷道的库位数 //当前巷道的库位数
List<Point> points = pointMapper.findByColAndLayer(currPoint.getColNum(), currPoint.getLayerNum()); List<Point> points = pointMapper.findByColAndLayer(currPoint.getColNum(), currPoint.getLayerNum());
@ -649,13 +765,10 @@ public class AllocateProcessor {
moveScore = (100.0 / (movePoints.size() + 1)) * 0.7; moveScore = (100.0 / (movePoints.size() + 1)) * 0.7;
} }
//库位得分 = 距离得分 + 移动得分 //库位得分 = 移动得分
double totalScore = distanceScore + moveScore; double totalScore = moveScore;
log.info("【{}】库位距离评分: {}-移位评分: {}-总得分: {}-移位次数: {}", return new InventoryScore(inventory, inventory.getStockId(), totalScore, 0, 0, movePoints, currOutPointCode, BigDecimal.ZERO, false);
currPoint.getPointCode(), distanceScore, moveScore, totalScore, movePoints.size());
return new InventoryScore(inventory, inventory.getStockId(), totalScore, movePoints, currOutPointCode);
} }
/** /**

View File

@ -5,6 +5,7 @@ import lombok.Data;
import org.cpte.modules.base.entity.Point; import org.cpte.modules.base.entity.Point;
import org.cpte.modules.inventory.entity.Inventory; import org.cpte.modules.inventory.entity.Inventory;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -15,12 +16,18 @@ public class InventoryScore {
private Inventory inventory; private Inventory inventory;
//容器 //容器
private Long stockId; private Long stockId;
// 分数 // 综合得分
private double score; private double totalScore;
//最佳出库口 // 匹配度得分
//private Point outPoint; private double matchScore;
// 移位得分
private double moveScore;
//移位的库位 //移位的库位
private List<Point> movePoints; private List<Point> movePoints;
// //当前出库库位
private String currOutPointCode; private String currOutPointCode;
// 可用数量
private BigDecimal availableQty;
// 是否完全匹配
private boolean perfectMatch;
} }