no message
parent
9719bcf427
commit
67f8506fae
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String> whCodeList = pickMap.values().stream().map(Pick::getWhCode).distinct().toList();
|
||||
List<String> projectList = pickDetails.stream().map(PickDetail::getProject).distinct().toList();
|
||||
List<String> taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).distinct().toList();
|
||||
List<String> propC1List = pickDetails.stream().map(PickDetail::getPropC1).distinct().toList();
|
||||
List<String> propC3List = pickDetails.stream().map(PickDetail::getPropC3).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().filter(s -> s != null && !s.trim().isEmpty()).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().filter(s -> s != null && !s.trim().isEmpty()).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);
|
||||
//出库明细指定了容器
|
||||
if (CollectionUtils.isNotEmpty(containerIds)) {
|
||||
|
|
@ -305,11 +306,11 @@ public class AllocateProcessor {
|
|||
data.setItemMap(itemMap);
|
||||
|
||||
//筛选查询库存的条件(非空去重)外部仓库、项目号、任务号、批次、外部库存状态
|
||||
List<String> whCodeList = pickMap.values().stream().map(Pick::getWhCode).distinct().toList();
|
||||
List<String> projectList = pickDetails.stream().map(PickDetail::getProject).distinct().toList();
|
||||
List<String> taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).distinct().toList();
|
||||
List<String> propC1List = pickDetails.stream().map(PickDetail::getPropC1).distinct().toList();
|
||||
List<String> propC3List = pickDetails.stream().map(PickDetail::getPropC3).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().filter(s -> s != null && !s.trim().isEmpty()).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().filter(s -> s != null && !s.trim().isEmpty()).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);
|
||||
//出库明细指定了容器
|
||||
if (CollectionUtils.isNotEmpty(containerIds)) {
|
||||
|
|
@ -522,7 +523,8 @@ public class AllocateProcessor {
|
|||
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) {
|
||||
|
|
@ -583,29 +585,148 @@ public class AllocateProcessor {
|
|||
* @param matchedInventories 库存集合
|
||||
* @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()
|
||||
.map(Inventory::getPointId)
|
||||
.toList();
|
||||
Map<Long, Point> pointMap = pointService.queryByPointIdsToMap(pointIds);
|
||||
|
||||
//获取出库口的库位
|
||||
// List<Point> 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<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.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<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();
|
||||
|
||||
// 计算距离分数(权重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());
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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<Point> movePoints;
|
||||
//
|
||||
//当前出库库位
|
||||
private String currOutPointCode;
|
||||
// 可用数量
|
||||
private BigDecimal availableQty;
|
||||
// 是否完全匹配
|
||||
private boolean perfectMatch;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue