no message

main
HUOJIN\92525 2025-12-07 23:11:32 +08:00
parent 5cdc1e3021
commit 126414e875
15 changed files with 284 additions and 64 deletions

View File

@ -47,6 +47,6 @@ public interface PointMapper extends BaseMapper<Point> {
@Select("SELECT COUNT(id) FROM base_point WHERE col_num = #{colNum} AND layer_num = #{layerNum} AND status = 1")
int countOccupiedInSameColumn(@Param("colNum") String colNum, @Param("layerNum") String layerNum);
@Select("SELECT * FROM base_point WHERE col_num = #{colNum} AND layer_num = #{layerNum} ORDER BY row_num ASC")
List<Point> findByColAndLayer(@Param("colNum") String colNum, @Param("layerNum") String layerNum);
}

View File

@ -22,18 +22,24 @@
JOIN base_area ba ON bp1.area_id = ba.id
JOIN base_point bp2 ON bp1.col_num = bp2.col_num
JOIN data_inventory inv ON bp2.id = inv.point_id
<where>
AND inv.item_id = #{itemId}
<if test="propC1 != null and propC1 != ''">
WHERE inv.item_id = #{itemId}
AND bp1.status = 0
AND ba.area_code = #{areaCode}
<choose>
<when test="propC1 != null and propC1 != ''">
AND inv.prop_c1 = #{propC1}
</if>
<if test="whCode != null and whCode != ''">
</when>
<otherwise>
AND (inv.prop_c1 IS NULL OR inv.prop_c1 = '')
</otherwise>
</choose>
<choose>
<when test="whCode != null and whCode != ''">
AND inv.wh_code = #{whCode}
</if>
<if test="areaCode != null and areaCode != ''">
AND ba.area_code = #{areaCode}
</if>
AND bp1.status = 0
</where>
</when>
<otherwise>
AND (inv.wh_code IS NULL OR inv.wh_code = '')
</otherwise>
</choose>
</select>
</mapper>

View File

@ -80,5 +80,21 @@ public interface IPointService extends IService<Point> {
*/
String getElevatorPoint(String pointCode, String key);
Point queryToPoint(String pointCode, Integer status, String areaCode);
/**
*
*
* @param colNum
* @return layerNum
*/
List<Point> findByColAndLayer(String colNum, String layerNum);
/**
*
*
* @param pointCode
* @param status
* @param areaCode
* @return List<Point>
*/
List<Point> queryPoints(String pointCode, Integer status, String areaCode);
}

View File

@ -144,12 +144,13 @@ public class PointServiceImpl extends ServiceImpl<PointMapper, Point> implements
}
@Override
public Point queryToPoint(String pointCode, Integer status, String areaCode) {
Point dstPoint = null;
List<Point> dstPointList = pointMapper.queryPoints(pointCode, status, areaCode);
if (dstPointList.isEmpty()) {
throw new RuntimeException("【" + AreaTypeEnum.CPCCQ.getDesc() + "】无空闲库位");
}
return dstPoint = dstPointList.get(0);
public List<Point> findByColAndLayer(String colNum, String layerNum) {
return pointMapper.findByColAndLayer(colNum, layerNum);
}
@Override
public List<Point> queryPoints(String pointCode, Integer status, String areaCode) {
return pointMapper.queryPoints(pointCode, status, areaCode);
}
}

View File

@ -15,9 +15,9 @@ public enum AreaTypeEnum {
MJCCQ("MJCCQ", "模具存储区"),
RK_DOCK("RK_DOCK", "入库输送线接驳口"),
RK_DOCK("RK_DOCK", "入库工作站"),
CK_DOCK("CK_DOCK", "出库输送线接驳口"),
CK_DOCK("CK_DOCK", "出库工作站"),
;
/**
*

View File

@ -7,12 +7,10 @@ import lombok.Data;
@Data
public class ScanTrayRequest {
//托盘码
@NotBlank(message = "托盘码不能为空")
@JsonProperty("stockCode")
private String stockCode;
//工作站
@NotBlank(message = "工作站不能为空")
@JsonProperty("station")
private String station;
}

View File

@ -65,9 +65,21 @@ public class IConveyorLineServiceImpl implements IConveyorLineService {
@Autowired
private IAgvTaskService iAgvTaskService;
private void validateParams(ScanTrayRequest scanTrayRequest) {
if (StringUtils.isBlank(scanTrayRequest.getStockCode())) {
throw new RuntimeException("托盘码不能为空");
}
if (StringUtils.isBlank(scanTrayRequest.getStation())) {
throw new RuntimeException("工作站不能为空");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void scanTray(ScanTrayRequest scanTrayRequest) {
//验证参数
validateParams(scanTrayRequest);
//工作站
Point srcPoint = pointMapper.queryByPointCode(scanTrayRequest.getStation());

View File

@ -35,7 +35,7 @@ public interface InventoryMapper extends BaseMapper<Inventory> {
* @param whCodeList
* @return List<Inventory>
*/
List<Inventory> queryInventoryWithLock(@Param("itemIds") List<Long> itemIds, @Param("propC1List") List<String> propC1List, @Param("propC3List") List<String> propC3List, @Param("whCodeList") List<String> whCodeList);
List<Inventory> queryInventory(@Param("itemIds") List<Long> itemIds, @Param("propC1List") List<String> propC1List, @Param("propC3List") List<String> propC3List, @Param("whCodeList") List<String> whCodeList);
// 查询相邻库位同一巷道深度±3范围内
@Select("SELECT di.* " +

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.cpte.modules.inventory.mapper.InventoryMapper">
<select id="queryInventoryWithLock" resultType="org.cpte.modules.inventory.entity.Inventory">
<select id="queryInventory" resultType="org.cpte.modules.inventory.entity.Inventory">
SELECT * FROM data_inventory
WHERE status in (1,2)
AND quantity > 0
@ -9,25 +9,41 @@
<foreach collection="itemIds" item="itemId" open="(" separator="," close=")">
#{itemId}
</foreach>
<if test="propC1List != null and !propC1List.isEmpty()">
AND prop_c1 IN
<foreach collection="propC1List" item="propC1" open="(" separator="," close=")">
#{propC1}
</foreach>
</if>
<if test="propC3List != null and !propC3List.isEmpty()">
AND prop_c3 IN
<foreach collection="propC3List" item="propC3" open="(" separator="," close=")">
#{propC3}
</foreach>
</if>
<if test="whCodeList != null and !whCodeList.isEmpty()">
AND wh_code IN
<foreach collection="whCodeList" item="whCode" open="(" separator="," close=")">
#{whCode}
</foreach>
</if>
<choose>
<when test="propC1List != null and !propC1List.isEmpty()">
AND prop_c1 IN
<foreach collection="propC1List" item="propC1" open="(" separator="," close=")">
#{propC1}
</foreach>
</when>
<otherwise>
AND (prop_c1 IS NULL OR prop_c1 = '')
</otherwise>
</choose>
<choose>
<when test="propC3List != null and !propC3List.isEmpty()">
AND prop_c3 IN
<foreach collection="propC3List" item="propC3" open="(" separator="," close=")">
#{propC3}
</foreach>
</when>
<otherwise>
AND (prop_c3 IS NULL OR prop_c3 = '')
</otherwise>
</choose>
<choose>
<when test="whCodeList != null and !whCodeList.isEmpty()">
AND wh_code IN
<foreach collection="whCodeList" item="whCode" open="(" separator="," close=")">
#{whCode}
</foreach>
</when>
<otherwise>
AND (wh_code IS NULL OR wh_code = '')
</otherwise>
</choose>
ORDER BY create_time
FOR UPDATE
</select>
</mapper>

View File

@ -49,12 +49,13 @@ public interface IInventoryLogService extends IService<InventoryLog> {
*
*
* @param inventory
* @param dstPointId
* @param AllocatedQty
* @param businessNo
* @param businessDetailId ID
* @param description
*/
void addAllocInventoryLog(Inventory inventory, BigDecimal AllocatedQty, String businessNo, Long businessDetailId, String description);
void addAllocInventoryLog(Inventory inventory, Long dstPointId, BigDecimal AllocatedQty, String businessNo, Long businessDetailId, String description);
/**

View File

@ -61,10 +61,12 @@ public class InventoryLogServiceImpl extends ServiceImpl<InventoryLogMapper, Inv
@Override
@Transactional(rollbackFor = Exception.class)
public void addAllocInventoryLog(Inventory inventory, BigDecimal AllocatedQty, String businessNo, Long businessDetailId, String description) {
public void addAllocInventoryLog(Inventory inventory,Long dstPointId, BigDecimal AllocatedQty, String businessNo, Long businessDetailId, String description) {
InventoryLog inventoryLog = buildInventoryLog(inventory, BigDecimal.ZERO, businessNo, businessDetailId, description);
//出库分配
inventoryLog.setLogType(InventoryLogEnum.ALLOC.getValue());
inventoryLog.setFromPointId(inventory.getPointId());
inventoryLog.setToPointId(dstPointId);
//实际数量不变
inventoryLog.setChangeQty(BigDecimal.ZERO);
inventoryLog.setBeforeAllocatedQty(BigDecimalUtil.subtract(inventory.getQueuedQty(), AllocatedQty, 0));

View File

@ -149,7 +149,7 @@ public class ISaiWmsServiceImpl implements ISaiWmsService {
Stock stock = iStockService.validateStock(stockCode);
//获取输送线工作台点位,均衡分配点位任务-轮询方式
Point dstPoint = iPointService.getWorkStationPoint(CommonStatusEnum.FREE.getValue(), AreaTypeEnum.RK_DOCK.getValue(), GeneralConstant.RK_DOCK_TASK_INDEX);
Point dstPoint = iPointService.getWorkStationPoint(null, AreaTypeEnum.RK_DOCK.getValue(), GeneralConstant.RK_DOCK_TASK_INDEX);
// 创建入库单和明细
Asn createAsn = buildAsn(inboundRequest);

View File

@ -14,6 +14,7 @@ import org.cpte.modules.base.service.IItemService;
import org.cpte.modules.base.service.IPointService;
import org.cpte.modules.base.service.IStockService;
import org.cpte.modules.constant.enums.*;
import org.cpte.modules.conveyorLine.vo.PointScore;
import org.cpte.modules.inventory.entity.Inventory;
import org.cpte.modules.inventory.mapper.InventoryMapper;
import org.cpte.modules.inventory.service.IInventoryService;
@ -27,6 +28,7 @@ import org.cpte.modules.shipping.mapper.TaskMapper;
import org.cpte.modules.shipping.service.IPickService;
import org.cpte.modules.shipping.service.ITaskService;
import org.cpte.modules.shipping.vo.InventoryGroupKey;
import org.cpte.modules.shipping.vo.InventoryScore;
import org.cpte.modules.utils.BatchUtil;
import org.cpte.modules.utils.BigDecimalUtil;
import org.jeecg.common.constant.CommonConstant;
@ -318,7 +320,7 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
List<String> whCodeList = pickMap.values().stream().map(Pick::getWhCode).filter(StringUtils::hasText).distinct().toList();
//查询库存
List<Inventory> inventories = inventoryMapper.queryInventoryWithLock(itemIds, propC1List, propC3List, whCodeList);
List<Inventory> inventories = inventoryMapper.queryInventory(itemIds, propC1List, propC3List, whCodeList);
if (CollectionUtils.isEmpty(inventories)) {
String itemCodes = itemMap.values().stream().map(Item::getItemCode).collect(Collectors.joining(","));
errorMsgSet.add("【" + itemCodes + "】物料库存不足");
@ -374,14 +376,14 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
continue;
}
//获取输送线工作台点位,均衡分配点位任务-轮询方式
Point toPoint = iPointService.getWorkStationPoint(CommonStatusEnum.FREE.getValue(), AreaTypeEnum.CK_DOCK.getValue(), GeneralConstant.CK_DOCK_TASK_INDEX);
//记录当前容器
Long currStockId = 0L;
for (Inventory inventory : matchedInventories) {
//智能排序,优先分配移位最小的库位
List<InventoryScore> scoredInventory = scoreInventories(matchedInventories);
for (InventoryScore inventoryScore : scoredInventory) {
if (unAllocatedQty.compareTo(BigDecimal.ZERO) <= 0) {
break;
}
Inventory inventory = inventoryScore.getInventory();
// 库存可用数量
BigDecimal availableQty = BigDecimalUtil.subtract(inventory.getQuantity(), inventory.getQueuedQty(), 0);
if (availableQty.compareTo(BigDecimal.ZERO) <= 0) {
@ -405,20 +407,26 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
Point fromPoint = pointMap.get(inventory.getPointId());
//是否整托:0整托、1拆托
Integer izAll = availableQty.compareTo(allocateQty) == 0 ? 0 : 1;
//判断当前容器是否一致,同一个容器去同一个目标库位
if (!currStockId.equals(stock.getId())) {
toPoint = iPointService.getWorkStationPoint(CommonStatusEnum.FREE.getValue(), AreaTypeEnum.CK_DOCK.getValue(), GeneralConstant.CK_DOCK_TASK_INDEX);
}
//目标库位
Point toPoint = inventoryScore.getOutPoint();
//构建拣货任务存入集合批量新增
Task task = iTaskService.bulidTask(pick.getNo() + "_" + pickDetail.getLineNo(), TaskTypeEnum.PICK.getValue(), item, fromPoint, toPoint, stock, pick.getId(), pickDetail.getId(), inventory.getId(), allocateQty, izAll);
createToTask.add(task);
log.info("生成拣货任务:{}- 容器:{} - 库位:{} - 类型:{}- 分配数量:{}", task.getTaskNo(), stock.getStockCode(), fromPoint.getPointCode(), izAll, allocateQty);
//移位任务
if (CollectionUtils.isNotEmpty(inventoryScore.getMovePoints())) {
for (Point movePoint : inventoryScore.getMovePoints()) {
log.info("生成移位任务:原库位:{}", movePoint.getPointCode());
}
} else {
log.info("无移位任务");
}
//分配库存日志
iInventoryLogService.addAllocInventoryLog(inventory, allocateQty, pick.getOrderNo(), pickDetail.getId(), pickDetail.getDescription());
iInventoryLogService.addAllocInventoryLog(inventory, toPoint.getId(), allocateQty, pick.getOrderNo(), pickDetail.getId(), pickDetail.getDescription());
//更新未分配数量
unAllocatedQty = BigDecimalUtil.subtract(unAllocatedQty, allocateQty, 0);
//更新当前容器
currStockId = stock.getId();
}
//分配后仍有缺货,记录错误
if (unAllocatedQty.compareTo(BigDecimal.ZERO) > 0) {
@ -453,6 +461,145 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
return new ArrayList<>(errorMsgSet);
}
/**
*
*
* @param matchedInventories
* @return List<InventoryScore>
*/
private List<InventoryScore> scoreInventories(List<Inventory> matchedInventories) {
// 批量查询库位
List<Long> pointIds = matchedInventories.stream()
.map(Inventory::getPointId)
.toList();
Map<Long, Point> pointMap = iPointService.queryByPointIdsToMap(pointIds);
// 按巷道和层分组
Map<String, List<Point>> colLayerPointsMap = new HashMap<>();
for (Point point : pointMap.values()) {
String key = point.getColNum() + "-" + point.getLayerNum();
if (colLayerPointsMap.containsKey(key)) {
continue;
}
List<Point> points = iPointService.findByColAndLayer(point.getColNum(), point.getLayerNum());
colLayerPointsMap.put(key, points);
}
//获取所以出库口的库位
List<Point> outPoints = iPointService.queryPoints(null, null, AreaTypeEnum.CK_DOCK.getValue());
//获取优化后的库存
return matchedInventories.stream()
.map(inventory -> {
Point currPoint = pointMap.get(inventory.getPointId());
String key = currPoint.getColNum() + "-" + currPoint.getLayerNum();
List<Point> points = colLayerPointsMap.get(key);
return calculateMoveCount(inventory, currPoint, points, outPoints);
})
.sorted(Comparator.comparing(InventoryScore::getScore).reversed())
.toList();
}
/**
*
*
* @param inventory
* @param currPoint
* @param points
* @return
*/
private InventoryScore calculateMoveCount(Inventory inventory, Point currPoint, List<Point> points, List<Point> outPoints) {
double totalScore = 0.0;
// 计算距离分数权重30%
Point bestPoint = getBestOutboundPoint(currPoint, outPoints);
double distanceScore = calculateClusterDistanceCost(currPoint, bestPoint);
totalScore += distanceScore * 0.3;
// 目标库位的深度位转换为索引
int targetIndex = Integer.parseInt(currPoint.getRowNum()) - 1;
//双通道
if (currPoint.getIzDoubleLane().equals(1)) {
// 计算左侧占用数
int leftOccupied = 0;
List<Point> leftPoints = new ArrayList<>();
for (int i = 0; i < targetIndex; i++) {
Point point = points.get(i);
if (point != null && point.getStatus().equals(CommonStatusEnum.USED.getValue())) {
leftOccupied++;
leftPoints.add(point);
}
}
// 计算右侧占用数
int rightOccupied = 0;
List<Point> rightPoints = new ArrayList<>();
for (int i = targetIndex + 1; i < points.size(); i++) {
Point point = points.get(i);
if (point != null && point.getStatus().equals(CommonStatusEnum.USED.getValue())) {
rightOccupied++;
rightPoints.add(point);
}
}
//取两个集合中,元素最少的那个集合
List<Point> movePoints = leftOccupied < rightOccupied ? leftPoints : rightPoints;
// 取最小值,移位越小分数越高
int minOccupied = Math.min(leftOccupied, rightOccupied);
if (minOccupied == 0) {
minOccupied = 1; // 设置默认值防止除以0
}
totalScore += 100.0 / minOccupied * 0.7;
return new InventoryScore(inventory, totalScore, bestPoint, movePoints);
} else {
// 单通道,计算目标位置左侧的占用数
int leftOccupied = 0;
List<Point> movePoints = new ArrayList<>();
for (int i = 0; i < targetIndex; i++) {
Point point = points.get(i);
if (point != null && point.getStatus().equals(CommonStatusEnum.USED.getValue())) {
leftOccupied++;
movePoints.add(point);
}
}
if (leftOccupied == 0) {
leftOccupied = 1;
}
totalScore += 100.0 / leftOccupied * 0.7;
return new InventoryScore(inventory, totalScore, bestPoint, movePoints);
}
}
/**
* @param currPoint
* @param outPoints
* @return
*/
private Point getBestOutboundPoint(Point currPoint, List<Point> outPoints) {
//获取距离最近的出库口
return outPoints.stream()
.min(Comparator.comparingDouble(point ->
Math.abs(currPoint.getPositionX() - point.getPositionX()) +
Math.abs(currPoint.getPositionY() - point.getPositionY())))
.orElse(null);
}
/**
*
*
*/
private double calculateClusterDistanceCost(Point point, Point station) {
// 计算曼哈顿距离
double distance = Math.abs(point.getPositionX() - station.getPositionX()) + Math.abs(point.getPositionY() - station.getPositionY());
// 距离越小分数越高
return Math.max(0, 100 - (distance / 100.0));
}
@Override
@Transactional(rollbackFor = Exception.class)
public List<String> cancelAllocate(List<Long> pickIds) {
@ -528,7 +675,7 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
@Override
@Transactional(rollbackFor = Exception.class)
public void pickTask(List<Task> tasks,Point endPoint) {
public void pickTask(List<Task> tasks, Point endPoint) {
if (CollectionUtils.isEmpty(tasks)) {
throw new RuntimeException("无拣货任务");
}

View File

@ -0,0 +1,21 @@
package org.cpte.modules.shipping.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.inventory.entity.Inventory;
import java.util.List;
@Data
@AllArgsConstructor
public class InventoryScore {
// 库存
private Inventory inventory;
// 分数
private double score;
//最佳出库口
private Point outPoint;
//移位的库位
private List<Point> movePoints;
}

View File

@ -178,7 +178,7 @@ mybatis-plus:
table-underline: true
configuration:
# # 这个配置会将执行的sql打印出来在开发或测试的时候可以用
#log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 返回类型为Map,显示null对应的字段
call-setters-on-nulls: true
#jeecg专用配置