no message
parent
9719bcf427
commit
67f8506fae
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue