no message

main
HUOJIN\92525 2026-01-07 22:47:29 +08:00
parent d73d0d454f
commit d8b84477ad
21 changed files with 2746 additions and 120 deletions

View File

@ -181,7 +181,7 @@ public class ScanTrayProcessor {
*/ */
private String getAreaCode(Integer orderType) { private String getAreaCode(Integer orderType) {
String areaCode = ""; String areaCode = "";
if (Set.of(0, 1, 2, 3).contains(orderType)) { if (Set.of(0, 1, 2, 3, 8).contains(orderType)) {
areaCode = AreaTypeEnum.CPCCQ.getValue(); areaCode = AreaTypeEnum.CPCCQ.getValue();
} else { } else {
areaCode = AreaTypeEnum.MJCCQ.getValue(); areaCode = AreaTypeEnum.MJCCQ.getValue();
@ -199,51 +199,32 @@ public class ScanTrayProcessor {
* @return * @return
*/ */
public Point allocatePoint(List<ItemKey> itemKeys, Point station, String areaCode, String type) { public Point allocatePoint(List<ItemKey> itemKeys, Point station, String areaCode, String type) {
//1.优先寻找同物料/同仓库/同项目号/同任务号/同批次/同外部库存状态的库位 // 1. 获取可用库位
List<Long> itemKeyIds = itemKeys.stream().map(ItemKey::getId).toList(); List<Point> availablePoints = getAvailablePoints(itemKeys, areaCode, type);
List<Point> availablePoints = pointService.findClusterPoint(itemKeyIds, areaCode);
if (CollectionUtils.isEmpty(availablePoints)) { if (CollectionUtils.isEmpty(availablePoints)) {
//2.获取所有可用库位 throw new RuntimeException("系统无可用库位");
availablePoints = pointMapper.queryPoints(null, CommonStatusEnum.FREE.getValue(), areaCode);
} }
//根据巷道分组,得到每个巷道有多个库位,<巷道编号,库位个数> //根据巷道分组,得到每个巷道有多个库位,<巷道编号,库位个数>
Map<String, Long> colMap = getColMap(areaCode); Map<String, Long> colMap = getColMap(areaCode);
//根据layerNum层来分组 //根据layerNum层来分组
Map<String, List<Point>> layerMap = availablePoints.stream().collect(Collectors.groupingBy(Point::getLayerNum)); Map<String, List<Point>> layerMap = availablePoints.stream().collect(Collectors.groupingBy(Point::getLayerNum));
List<PointScore> firstScoredPoints = new ArrayList<>(); List<PointScore> firstScoredPoints;
List<PointScore> secondScoredPoints = new ArrayList<>(); List<PointScore> secondScoredPoints = new ArrayList<>();
//移位选择最优库位 //移位选择最优库位
if (type.equals(BusinessTypeEnum.MOVE.getValue())) { if (type.equals(BusinessTypeEnum.MOVE.getValue())) {
String layerNum = station.getLayerNum(); firstScoredPoints = selectBestPointForMove(station, itemKeys,colMap,layerMap);
Station currentStation = new Station(station.getPositionX(), station.getPositionY());
List<Point> firstLevelPoints = layerMap.get(layerNum);
if (CollectionUtils.isNotEmpty(firstLevelPoints)) {
firstScoredPoints = firstLevelPoints.stream()
.map(point -> clusterPointScore(point, currentStation, itemKeys, colMap))
.toList();
}
} else { } else {
//入库选择最优库位
//计算第一层的库位分数 //计算第一层的库位分数
Station firstStation = new Station(station.getPositionX(), station.getPositionY()); Station firstStation = new Station(station.getPositionX(), station.getPositionY());
List<Point> firstLevelPoints = layerMap.get(String.valueOf(CommonStatusEnum.ONE.getValue())); firstScoredPoints = selectBestPointForStorage(firstStation, itemKeys, CommonStatusEnum.ONE.getValue(), colMap, layerMap);
if (CollectionUtils.isNotEmpty(firstLevelPoints)) {
firstScoredPoints = firstLevelPoints.stream()
.map(point -> clusterPointScore(point, firstStation, itemKeys, colMap))
.toList();
}
//计算第二层的库位分数 //计算第二层的库位分数
Station secondStation = new Station(station.getPositionTwoX(), station.getPositionTwoY()); Station secondStation = new Station(station.getPositionTwoX(), station.getPositionTwoY());
List<Point> secondLevelPoints = layerMap.get(String.valueOf(CommonStatusEnum.TWO.getValue())); secondScoredPoints= selectBestPointForStorage(secondStation, itemKeys, CommonStatusEnum.TWO.getValue(), colMap, layerMap);
if (CollectionUtils.isNotEmpty(secondLevelPoints)) {
secondScoredPoints = secondLevelPoints.stream()
.map(point -> clusterPointScore(point, secondStation, itemKeys, colMap))
.toList();
}
} }
//合并获取最优库位 //合并获取最优库位
@ -254,7 +235,7 @@ public class ScanTrayProcessor {
.sorted(Comparator.comparing(PointScore::getScore).reversed()) .sorted(Comparator.comparing(PointScore::getScore).reversed())
.toList(); .toList();
if (!scoredPoints.isEmpty()) { if (CollectionUtils.isNotEmpty(scoredPoints)) {
log.info("最优【{}】库位评分结果:{}", scoredPoints.get(0).getPoint().getPointCode(), scoredPoints.get(0).getScore()); log.info("最优【{}】库位评分结果:{}", scoredPoints.get(0).getPoint().getPointCode(), scoredPoints.get(0).getScore());
return scoredPoints.get(0).getPoint(); return scoredPoints.get(0).getPoint();
} }
@ -262,6 +243,60 @@ public class ScanTrayProcessor {
throw new RuntimeException("系统无可用库位"); throw new RuntimeException("系统无可用库位");
} }
/**
*
*/
private List<Point> getAvailablePoints(List<ItemKey> itemKeys, String areaCode, String type) {
//移位
if (BusinessTypeEnum.MOVE.getValue().equals(type)) {
return pointMapper.queryPoints(null, CommonStatusEnum.FREE.getValue(), areaCode);
}
//入库
List<Long> itemKeyIds = itemKeys.stream().map(ItemKey::getId).toList();
List<Point> availablePoints = pointService.findClusterPoint(itemKeyIds, areaCode);
if (CollectionUtils.isEmpty(availablePoints)) {
//2.获取所有可用库位
availablePoints = pointMapper.queryPoints(null, CommonStatusEnum.FREE.getValue(), areaCode);
}
return availablePoints;
}
/**
*
*/
private List<PointScore> selectBestPointForMove(Point station, List<ItemKey> itemKeys, Map<String, Long> colMap, Map<String, List<Point>> layerMap) {
//当前库位的层数
String layerNum = station.getLayerNum();
//当前库位的坐标
Station currentStation = new Station(station.getPositionX(), station.getPositionY());
//当前层的所有可用库位
List<Point> currentLayerPoints = layerMap.get(layerNum);
//过滤掉当前巷道的库位
currentLayerPoints = currentLayerPoints.stream().filter(point -> !point.getColNum().equals(station.getColNum())).toList();
if (CollectionUtils.isNotEmpty(currentLayerPoints)) {
return currentLayerPoints.stream()
.map(point -> clusterPointScore(point, currentStation, itemKeys, colMap))
.toList();
}
return new ArrayList<>();
}
/**
*
*/
private List<PointScore> selectBestPointForStorage(Station station, List<ItemKey> itemKeys, Integer layerNum, Map<String, Long> colMap, Map<String, List<Point>> layerMap) {
List<Point> currLayerPoints = layerMap.get(String.valueOf(layerNum));
if (CollectionUtils.isNotEmpty(currLayerPoints)) {
return currLayerPoints.stream()
.map(point -> clusterPointScore(point, station, itemKeys, colMap))
.toList();
}
return new ArrayList<>();
}
/** /**
* *
*/ */

View File

@ -11,6 +11,7 @@ import org.quartz.Job;
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -41,37 +42,53 @@ public class AllocateJob implements Job {
// 缓存最大大小,防止内存溢出 // 缓存最大大小,防止内存溢出
private static final int MAX_CACHE_SIZE = 1000; private static final int MAX_CACHE_SIZE = 1000;
// 使用静态变量跟踪当前分配位置
private static volatile int currentIndex = 0;
@Override @Override
public void execute(JobExecutionContext jobExecutionContext) { public void execute(JobExecutionContext jobExecutionContext) {
// 查询未分配或者部分分配的出库 // 查询未分配或者部分分配的出库
List<Long> pickList = pickMapper.queryUnallocatedPick(); List<Long> pickList = pickMapper.queryUnallocatedPick();
if (CollectionUtils.isNotEmpty(pickList)) { if (CollectionUtils.isNotEmpty(pickList)) {
// 分配出库 // 轮询分配每次只处理一个ID
long startTime = System.currentTimeMillis(); if (currentIndex < pickList.size()) {
List<String> resultMsg = pickService.allocatePick(pickList); Long currentPickId = pickList.get(currentIndex);
long endTime = System.currentTimeMillis(); List<Long> singlePickList = List.of(currentPickId);
log.info("分配出库明细耗时:{}ms", endTime - startTime); long startTime = System.currentTimeMillis();
if (CollectionUtils.isNotEmpty(resultMsg)) { List<String> resultMsg;
// 生成缓存键 try {
String cacheKey = generateCacheKey(resultMsg); resultMsg = pickService.allocatePick(singlePickList);
}catch (Exception e){
resultMsg=List.of(e.getMessage());
}
long endTime = System.currentTimeMillis();
log.info("分配出库明细耗时:{}ms处理ID{}", endTime - startTime, currentPickId);
// 检查是否已经处理过相同的内容 if (CollectionUtils.isNotEmpty(resultMsg)) {
if (!processedCache.containsKey(cacheKey)) { // 生成缓存键
// 新内容,记录日志 String cacheKey = generateCacheKey(resultMsg);
baseCommonService.addLog("出库任务分配:" + "\n" + cacheKey, CommonConstant.LOG_TYPE_2, CommonConstant.OPERATE_TYPE_1);
// 添加到缓存 // 检查是否已经处理过相同的内容
processedCache.put(cacheKey, true); if (!processedCache.containsKey(cacheKey)) {
// 新内容,记录日志
baseCommonService.addLog("出库任务分配:" + "\n" + cacheKey, CommonConstant.LOG_TYPE_2, CommonConstant.OPERATE_TYPE_1);
// 控制缓存大小 // 添加到缓存
if (processedCache.size() > MAX_CACHE_SIZE) { processedCache.put(cacheKey, true);
// 移除一部分旧缓存(简单实现,可以按需优化)
processedCache.clear(); // 控制缓存大小
if (processedCache.size() > MAX_CACHE_SIZE) {
processedCache.clear();
}
} }
} }
// 更新索引,循环使用
currentIndex = (currentIndex + 1) % pickList.size();
} }
} }
//生成出库AGV出库任务 //生成出库AGV出库任务
long startTime2 = System.currentTimeMillis(); long startTime2 = System.currentTimeMillis();
iTaskService.generateAgvTask(); iTaskService.generateAgvTask();

View File

@ -64,6 +64,13 @@ public interface IAsnService extends IService<Asn> {
*/ */
void receiveAsn(Long asnId); void receiveAsn(Long asnId);
/**
*
*
* @param asn
*/
void cancelAsn(Asn asn);
} }

View File

@ -108,9 +108,9 @@ public class AsnDetailServiceImpl extends ServiceImpl<AsnDetailMapper, AsnDetail
stockService.bindStock(stock); stockService.bindStock(stock);
//4.成品入库生成AGV任务 //4.成品入库生成AGV任务
if (AsnOrderTypeEnum.PRODUCT.getValue().equals(inboundRequest.getType()) && station != null) { /*if (AsnOrderTypeEnum.PRODUCT.getValue().equals(inboundRequest.getType()) && station != null) {
agvTaskService.createAgvTask(asn.getId(), stock.getStockCode(), srcPoint.getPointCode(), station.getPointCode(), null, BusinessTypeEnum.INBOUND.getValue(), 0, AgvVendorEnum.HIK.getValue()); agvTaskService.createAgvTask(asn.getId(), stock.getStockCode(), srcPoint.getPointCode(), station.getPointCode(), null, BusinessTypeEnum.INBOUND.getValue(), 0, AgvVendorEnum.HIK.getValue());
} }*/
} }
/** /**

View File

@ -1,12 +1,15 @@
package org.cpte.modules.receive.service.impl; package org.cpte.modules.receive.service.impl;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.cpte.modules.agvTask.entity.AgvTask; import org.cpte.modules.agvTask.entity.AgvTask;
import org.cpte.modules.base.entity.Item; import org.cpte.modules.base.entity.Item;
import org.cpte.modules.base.entity.Point; import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock; import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.base.mapper.StockMapper;
import org.cpte.modules.constant.enums.AsnStatusEnum; import org.cpte.modules.constant.enums.AsnStatusEnum;
import org.cpte.modules.constant.enums.CommonStatusEnum;
import org.cpte.modules.receive.entity.Asn; import org.cpte.modules.receive.entity.Asn;
import org.cpte.modules.receive.entity.AsnDetail; import org.cpte.modules.receive.entity.AsnDetail;
import org.cpte.modules.receive.mapper.AsnDetailMapper; import org.cpte.modules.receive.mapper.AsnDetailMapper;
@ -36,6 +39,8 @@ import java.util.concurrent.atomic.AtomicInteger;
@Slf4j @Slf4j
public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnService { public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnService {
@Autowired
private StockMapper stockMapper;
@Autowired @Autowired
private AsnDetailMapper asnDetailMapper; private AsnDetailMapper asnDetailMapper;
@Autowired @Autowired
@ -54,7 +59,7 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
if (StringUtils.isEmpty(lockValue)) { if (StringUtils.isEmpty(lockValue)) {
throw new RuntimeException("入库单创建中,请稍后重试"); throw new RuntimeException("入库单创建中,请稍后重试");
} }
asnDetailService.processorSaveMain(asn, asnDetailList); asnDetailService.processorSaveMain(asn, asnDetailList);
} catch (Exception e) { } catch (Exception e) {
log.error("入库单创建异常", e); log.error("入库单创建异常", e);
throw e; throw e;
@ -119,6 +124,7 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
throw new RuntimeException("操作失败:【" + orderNoList + "】入库单,非创建状态不允许删除"); throw new RuntimeException("操作失败:【" + orderNoList + "】入库单,非创建状态不允许删除");
} }
} }
@Override @Override
public void receiveAsn(Long asnId) { public void receiveAsn(Long asnId) {
String lockKey = "asn:" + asnId; String lockKey = "asn:" + asnId;
@ -138,4 +144,28 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
} }
} }
} }
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelAsn(Asn asn) {
List<AsnDetail> details = asnDetailMapper.selectByMainId(asn.getId());
List<AsnDetail> updateToDetails = new ArrayList<>();
for (AsnDetail detail : details) {
detail.setStatus(AsnStatusEnum.CANCELED.getValue());
updateToDetails.add(detail);
}
if (CollectionUtils.isNotEmpty(updateToDetails)) {
asnDetailMapper.updateById(updateToDetails);
}
asn.setStatus(AsnStatusEnum.CANCELED.getValue());
this.baseMapper.updateById(asn);
if (CollectionUtils.isNotEmpty(details)) {
Stock stock = stockMapper.selectById(details.get(0).getStockId());
if (stock != null) {
stock.setStatus(CommonStatusEnum.FREE.getValue());
stockMapper.updateById(stock);
}
}
}
} }

View File

@ -78,6 +78,10 @@ public class ReceiveProcessor {
// 1.数据准备 // 1.数据准备
ReceiveData data = prepareReceiveData(asnId); ReceiveData data = prepareReceiveData(asnId);
if(CollectionUtils.isEmpty(data.getAsnDetails())){
return;
}
//3.创建数据结构 //3.创建数据结构
List<AsnDetail> updateToAsnDetail = new ArrayList<>(); List<AsnDetail> updateToAsnDetail = new ArrayList<>();
List<ReceiveRecord> createRecords = new ArrayList<>(); List<ReceiveRecord> createRecords = new ArrayList<>();

View File

@ -5,10 +5,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.cpte.modules.receive.service.IAsnService; import org.cpte.modules.receive.service.IAsnService;
import org.cpte.modules.saiWms.request.CallAgvRequest; import org.cpte.modules.saiWms.request.*;
import org.cpte.modules.saiWms.request.InboundRequest;
import org.cpte.modules.saiWms.request.OutboundRequest;
import org.cpte.modules.saiWms.request.SyncStockRequest;
import org.cpte.modules.saiWms.service.ISMOMService; import org.cpte.modules.saiWms.service.ISMOMService;
import org.jeecg.common.api.vo.Result; import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog; import org.jeecg.common.aspect.annotation.AutoLog;
@ -73,8 +70,21 @@ public class SaiWmsController {
@AutoLog(value = "出库任务下发") @AutoLog(value = "出库任务下发")
@Operation(summary = "赛意WMS-出库任务下发") @Operation(summary = "赛意WMS-出库任务下发")
@PostMapping(value = "/outBoundTask") @PostMapping(value = "/outBoundTask")
public Result<String> outBoundTask(@RequestBody @Valid OutboundRequest outboundRequest) { public Result<String> outBoundTask(@RequestBody OutboundRequest outboundRequest) {
iSaiWmsService.outBoundTask(outboundRequest); iSaiWmsService.outBoundTask(outboundRequest);
return Result.OK("操作成功!"); return Result.OK("操作成功!");
} }
/**
*
*
* @param inBoundCancel
*/
@AutoLog(value = "入库任务取消")
@Operation(summary = "入库任务取消")
@PostMapping(value = "/inBoundCancel")
public Result<String> inBoundCancel(@RequestBody InBoundCancelRequest inBoundCancel) {
iSaiWmsService.inBoundCancel(inBoundCancel);
return Result.OK("操作成功!");
}
} }

View File

@ -0,0 +1,11 @@
package org.cpte.modules.saiWms.request;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class InBoundCancelRequest {
// 任务号
@JsonProperty("No")
private String no;
}

View File

@ -15,17 +15,14 @@ import java.util.List;
@Data @Data
public class OutboundRequest { public class OutboundRequest {
// 任务号 // 任务号
@NotBlank(message = "任务号不能为空")
@JsonProperty("No") @JsonProperty("No")
private String no; private String no;
// 单号 // 单号
@NotBlank(message = "单号不能为空")
@JsonProperty("OrderNo") @JsonProperty("OrderNo")
private String orderNo; private String orderNo;
// 仓库 // 仓库
@NotBlank(message = "仓库不能为空")
@JsonProperty("WhCode") @JsonProperty("WhCode")
private String whCode; private String whCode;
@ -34,7 +31,6 @@ public class OutboundRequest {
private String customerCode; private String customerCode;
// 单据类型:0.成品入库;1.配件入库;2.成品拆托入库;3.配件拆托入库;4.成品出库;5.配件出库;6.返工出库;7.检验出库;8.其他出库 // 单据类型:0.成品入库;1.配件入库;2.成品拆托入库;3.配件拆托入库;4.成品出库;5.配件出库;6.返工出库;7.检验出库;8.其他出库
@NotNull(message = "单据类型不能为空")
@JsonProperty("Type") @JsonProperty("Type")
private Integer type; private Integer type;
@ -43,20 +39,16 @@ public class OutboundRequest {
private String enterprise; private String enterprise;
// 出库明细列表 // 出库明细列表
@NotNull(message = "入库明细不能为空")
@JsonProperty("details") @JsonProperty("details")
@Valid
private List<OutboundDetail> details; private List<OutboundDetail> details;
@Data @Data
public static class OutboundDetail { public static class OutboundDetail {
// 行号 // 行号
@NotBlank(message = "行号不能为空")
@JsonProperty("LineNo") @JsonProperty("LineNo")
private String lineNo; private String lineNo;
// 物料 // 物料
@NotBlank(message = "物料不能为空")
@JsonProperty("Item") @JsonProperty("Item")
private String item; private String item;
@ -66,10 +58,13 @@ public class OutboundRequest {
private String unit; private String unit;
// 数量 // 数量
@NotNull(message = "数量不能为空")
@JsonProperty("Qty") @JsonProperty("Qty")
private Double qty; private Double qty;
// 托盘号
@JsonProperty("Lpn")
private String lpn;
// 项目号 // 项目号
@JsonProperty("Project") @JsonProperty("Project")
private String project; private String project;

View File

@ -1,9 +1,7 @@
package org.cpte.modules.saiWms.service; package org.cpte.modules.saiWms.service;
import org.cpte.modules.saiWms.request.CallAgvRequest; import jakarta.validation.Valid;
import org.cpte.modules.saiWms.request.InboundRequest; import org.cpte.modules.saiWms.request.*;
import org.cpte.modules.saiWms.request.OutboundRequest;
import org.cpte.modules.saiWms.request.SyncStockRequest;
public interface ISMOMService { public interface ISMOMService {
@ -30,4 +28,9 @@ public interface ISMOMService {
* AGV * AGV
*/ */
void callAgv(CallAgvRequest callAgvRequest); void callAgv(CallAgvRequest callAgvRequest);
/**
*
*/
void inBoundCancel(InBoundCancelRequest inBoundCancel);
} }

View File

@ -2,15 +2,9 @@ package org.cpte.modules.saiWms.service.impl;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.cpte.modules.saiWms.request.CallAgvRequest; import org.cpte.modules.saiWms.request.*;
import org.cpte.modules.saiWms.request.InboundRequest;
import org.cpte.modules.saiWms.request.OutboundRequest;
import org.cpte.modules.saiWms.request.SyncStockRequest;
import org.cpte.modules.saiWms.service.ISMOMService; import org.cpte.modules.saiWms.service.ISMOMService;
import org.cpte.modules.saiWms.service.processor.CallAgvProcessor; import org.cpte.modules.saiWms.service.processor.*;
import org.cpte.modules.saiWms.service.processor.InBoundTaskProcessor;
import org.cpte.modules.saiWms.service.processor.OutBoundTaskProcessor;
import org.cpte.modules.saiWms.service.processor.SyncStockProcessor;
import org.cpte.modules.utils.RedisDistributedLockUtil; import org.cpte.modules.utils.RedisDistributedLockUtil;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -31,6 +25,9 @@ public class ISMOMServiceImpl implements ISMOMService {
@Autowired @Autowired
private CallAgvProcessor callAgvProcessor; private CallAgvProcessor callAgvProcessor;
@Autowired
private InBoundCancelProcessor inBoundCancelProcessor;
@Autowired @Autowired
private RedisDistributedLockUtil redissonLock; private RedisDistributedLockUtil redissonLock;
@ -100,6 +97,26 @@ public class ISMOMServiceImpl implements ISMOMService {
} }
} }
} }
@Override
public void inBoundCancel(InBoundCancelRequest inBoundCancel) {
String lockKey = "cancel:" + inBoundCancel.getNo();
String lockValue = null;
try {
lockValue = redissonLock.tryLock(lockKey, 10);
if (StringUtils.isEmpty(lockValue)) {
throw new RuntimeException("入库单取消中,请稍后重试");
}
inBoundCancelProcessor.inBoundCancel(inBoundCancel);
} catch (Exception e) {
log.error("入库单取消异常", e);
throw e;
} finally {
if (StringUtils.isNotEmpty(lockValue)) {
redissonLock.unlock(lockKey, lockValue);
}
}
}
} }

View File

@ -0,0 +1,93 @@
package org.cpte.modules.saiWms.service.processor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.cpte.modules.base.entity.Item;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock;
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.AsnOrderTypeEnum;
import org.cpte.modules.constant.enums.AsnStatusEnum;
import org.cpte.modules.inventory.mapper.InventoryMapper;
import org.cpte.modules.receive.entity.Asn;
import org.cpte.modules.receive.entity.AsnDetail;
import org.cpte.modules.receive.mapper.AsnDetailMapper;
import org.cpte.modules.receive.mapper.AsnMapper;
import org.cpte.modules.receive.service.IAsnDetailService;
import org.cpte.modules.receive.service.IAsnService;
import org.cpte.modules.saiWms.request.InBoundCancelRequest;
import org.cpte.modules.saiWms.request.InboundRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
*
*/
@Service
@Slf4j
public class InBoundCancelProcessor {
@Autowired
private AsnMapper asnMapper;
@Autowired
private IAsnService asnService;
/**
*
*
* @param inBoundCancel
*/
public void inBoundCancel(InBoundCancelRequest inBoundCancel) {
// 1.参数校验
validateParams(inBoundCancel);
// 2.验证任务号
Asn asn = validateAsn(inBoundCancel.getNo());
if (AsnStatusEnum.CANCELED.getValue().equals(asn.getStatus())) {
throw new RuntimeException("【" + inBoundCancel.getNo() + "】任务号已取消,请勿重复操作");
}
if (AsnStatusEnum.CREATED.getValue().equals(asn.getStatus())) {
asnService.cancelAsn(asn);
} else {
throw new RuntimeException("【" + inBoundCancel.getNo() + "】任务号已入库,无法取消");
}
}
/**
*
*
* @param inboundRequest
*/
private void validateParams(InBoundCancelRequest inboundRequest) {
if (StringUtils.isBlank(inboundRequest.getNo())) {
throw new RuntimeException("任务号(No)必填");
}
}
/**
*
*
* @param no
*/
private Asn validateAsn(String no) {
Asn asn = asnMapper.queryByNo(no);
if (asn == null) {
throw new RuntimeException("【" + no + "】任务号不存在");
}
return asn;
}
}

View File

@ -100,6 +100,11 @@ public class InBoundTaskProcessor {
if (inboundRequest.getType() == null) { if (inboundRequest.getType() == null) {
throw new RuntimeException("任务类型(Type)必填"); throw new RuntimeException("任务类型(Type)必填");
} }
if (!Set.of(0, 1, 2, 3, 8).contains(inboundRequest.getType())) {
throw new RuntimeException("【" + inboundRequest.getType() + "】任务类型错误");
}
if (AsnOrderTypeEnum.PRODUCT.getValue().equals(inboundRequest.getType())) { if (AsnOrderTypeEnum.PRODUCT.getValue().equals(inboundRequest.getType())) {
if (StringUtils.isBlank(inboundRequest.getLocationFrom())) { if (StringUtils.isBlank(inboundRequest.getLocationFrom())) {
throw new RuntimeException("起点(LocationFrom)必填"); throw new RuntimeException("起点(LocationFrom)必填");

View File

@ -5,8 +5,10 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.cpte.modules.base.entity.Item; import org.cpte.modules.base.entity.Item;
import org.cpte.modules.base.entity.Point; import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.base.service.IItemService; import org.cpte.modules.base.service.IItemService;
import org.cpte.modules.base.service.IPointService; import org.cpte.modules.base.service.IPointService;
import org.cpte.modules.base.service.IStockService;
import org.cpte.modules.constant.GeneralConstant; import org.cpte.modules.constant.GeneralConstant;
import org.cpte.modules.constant.enums.AreaTypeEnum; import org.cpte.modules.constant.enums.AreaTypeEnum;
import org.cpte.modules.constant.enums.AsnOrderTypeEnum; import org.cpte.modules.constant.enums.AsnOrderTypeEnum;
@ -22,6 +24,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* *
@ -36,6 +39,9 @@ public class OutBoundTaskProcessor {
@Autowired @Autowired
private IItemService itemService; private IItemService itemService;
@Autowired
private IStockService stockService;
@Autowired @Autowired
private IPointService pointService; private IPointService pointService;
@ -58,8 +64,11 @@ public class OutBoundTaskProcessor {
//3.验证物料 //3.验证物料
Map<String, Item> itemMap = validateItem(outboundRequest.getDetails()); Map<String, Item> itemMap = validateItem(outboundRequest.getDetails());
//4.验证容器
Map<String, Stock> stockMap = validateStock(outboundRequest.getDetails());
//5.出库处理 //5.出库处理
pickDetailService.processOutBoundTask(outboundRequest, itemMap); pickDetailService.processOutBoundTask(outboundRequest, itemMap,stockMap);
} }
/** /**
@ -81,6 +90,9 @@ public class OutBoundTaskProcessor {
throw new RuntimeException("任务类型(Type)必填"); throw new RuntimeException("任务类型(Type)必填");
} }
if (!Set.of(4, 5, 6, 7).contains(outboundRequest.getType())) {
throw new RuntimeException("【" + outboundRequest.getType() + "】任务类型错误");
}
if (CollectionUtils.isEmpty(outboundRequest.getDetails())) { if (CollectionUtils.isEmpty(outboundRequest.getDetails())) {
throw new RuntimeException("出库信息不能为空"); throw new RuntimeException("出库信息不能为空");
@ -140,6 +152,30 @@ public class OutBoundTaskProcessor {
return exitItemMap; return exitItemMap;
} }
/**
*
*
* @param detail
*/
private Map<String, Stock> validateStock(List<OutboundRequest.OutboundDetail> detail) {
//获取明细中所有的容器
List<String> stockCodes = detail.stream().map(OutboundRequest.OutboundDetail::getLpn).toList();
if (CollectionUtils.isNotEmpty(stockCodes)) {
//获取数据库已存在容器
Map<String, Stock> exitStockMap = stockService.queryByStockCodesToMap(stockCodes);
//获取不存在的容器
List<String> notExitStockCodes = stockCodes.stream().filter(stockCode -> !exitStockMap.containsKey(stockCode)).toList();
if (CollectionUtils.isNotEmpty(notExitStockCodes)) {
throw new RuntimeException("系统无" + notExitStockCodes + "托盘,请维护");
}
return exitStockMap;
}
return null;
}
/** /**
* *
* *

View File

@ -61,6 +61,15 @@ public class PickDetail implements Serializable {
@Schema(description = "物料ID") @Schema(description = "物料ID")
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private java.lang.Long itemId; private java.lang.Long itemId;
/**
*
*/
@Excel(name = "容器", width = 15)
@Schema(description = "容器")
@JsonSerialize(using = ToStringSerializer.class)
private java.lang.Long stockId;
/** /**
* *
*/ */

View File

@ -1,14 +1,11 @@
package org.cpte.modules.shipping.service; package org.cpte.modules.shipping.service;
import org.cpte.modules.base.entity.Item; import org.cpte.modules.base.entity.Item;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock; import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.inventory.entity.Inventory;
import org.cpte.modules.saiWms.request.OutboundRequest; import org.cpte.modules.saiWms.request.OutboundRequest;
import org.cpte.modules.shipping.entity.Pick; import org.cpte.modules.shipping.entity.Pick;
import org.cpte.modules.shipping.entity.PickDetail; import org.cpte.modules.shipping.entity.PickDetail;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import org.cpte.modules.shipping.entity.Task;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -36,7 +33,7 @@ public interface IPickDetailService extends IService<PickDetail> {
* @param exitItemMap * @param exitItemMap
* @return * @return
*/ */
List<PickDetail> buildPickDetail(List<OutboundRequest.OutboundDetail> details, Map<String, Item> exitItemMap); List<PickDetail> buildPickDetail(List<OutboundRequest.OutboundDetail> details, Map<String, Item> exitItemMap, Map<String, Stock> stockMap);
/** /**
@ -77,7 +74,7 @@ public interface IPickDetailService extends IService<PickDetail> {
* @param outboundRequest * @param outboundRequest
* @param itemMap * @param itemMap
*/ */
void processOutBoundTask(OutboundRequest outboundRequest, Map<String, Item> itemMap); void processOutBoundTask(OutboundRequest outboundRequest, Map<String, Item> itemMap, Map<String, Stock> stockMap);
/** /**
* *

View File

@ -3,6 +3,7 @@ package org.cpte.modules.shipping.service.impl;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
import org.cpte.modules.base.entity.Item; import org.cpte.modules.base.entity.Item;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.constant.GeneralConstant; import org.cpte.modules.constant.GeneralConstant;
import org.cpte.modules.constant.enums.PickStatusEnum; import org.cpte.modules.constant.enums.PickStatusEnum;
import org.cpte.modules.saiWms.request.OutboundRequest; import org.cpte.modules.saiWms.request.OutboundRequest;
@ -56,12 +57,13 @@ public class PickDetailServiceImpl extends ServiceImpl<PickDetailMapper, PickDet
} }
@Override @Override
public List<PickDetail> buildPickDetail(List<OutboundRequest.OutboundDetail> details, Map<String, Item> exitItemMap) { public List<PickDetail> buildPickDetail(List<OutboundRequest.OutboundDetail> details, Map<String, Item> exitItemMap, Map<String, Stock> stockMap) {
List<PickDetail> newDetailList = new ArrayList<>(); List<PickDetail> newDetailList = new ArrayList<>();
for (OutboundRequest.OutboundDetail detail : details) { for (OutboundRequest.OutboundDetail detail : details) {
PickDetail pickDetail = PickDetail.builder() PickDetail pickDetail = PickDetail.builder()
.lineNo(Integer.parseInt(detail.getLineNo())) .lineNo(Integer.parseInt(detail.getLineNo()))
.itemId(exitItemMap.get(detail.getItem()).getId()) .itemId(exitItemMap.get(detail.getItem()).getId())
.stockId(stockMap == null ? null : stockMap.get(detail.getLpn()).getId())
.unit(detail.getUnit()) .unit(detail.getUnit())
.orderQty(BigDecimal.valueOf(detail.getQty())) .orderQty(BigDecimal.valueOf(detail.getQty()))
.allocatedQty(BigDecimal.ZERO) .allocatedQty(BigDecimal.ZERO)
@ -77,23 +79,23 @@ public class PickDetailServiceImpl extends ServiceImpl<PickDetailMapper, PickDet
return newDetailList; return newDetailList;
} }
/** /**
* Map * Map
* *
* @param pickIds id * @param pickIds id
* @return Map * @return Map
*/ */
public Map<Long, Pick> queryByPickIdsToMap(List<Long> pickIds) { public Map<Long, Pick> queryByPickIdsToMap(List<Long> pickIds) {
if (CollectionUtils.isEmpty(pickIds)) { if (CollectionUtils.isEmpty(pickIds)) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
Map<Long, Pick> pickMap = new HashMap<>(); Map<Long, Pick> pickMap = new HashMap<>();
List<Pick> pickList = pickMapper.selectByIds(pickIds); List<Pick> pickList = pickMapper.selectByIds(pickIds);
for (Pick pick : pickList) { for (Pick pick : pickList) {
pickMap.put(pick.getId(), pick); pickMap.put(pick.getId(), pick);
} }
return pickMap; return pickMap;
} }
/** /**
* Map * Map
@ -146,10 +148,10 @@ public class PickDetailServiceImpl extends ServiceImpl<PickDetailMapper, PickDet
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void processOutBoundTask(OutboundRequest outboundRequest, Map<String, Item> itemMap) { public void processOutBoundTask(OutboundRequest outboundRequest, Map<String, Item> itemMap, Map<String, Stock> stockMap) {
// 创建出库单和明细 // 创建出库单和明细
Pick createPick = buildPick(outboundRequest); Pick createPick = buildPick(outboundRequest);
List<PickDetail> pickDetails = buildPickDetail(outboundRequest.getDetails(), itemMap); List<PickDetail> pickDetails = buildPickDetail(outboundRequest.getDetails(), itemMap, stockMap);
processorSaveMain(createPick, pickDetails); processorSaveMain(createPick, pickDetails);
} }

View File

@ -23,7 +23,6 @@ import org.cpte.modules.shipping.entity.Pick;
import org.cpte.modules.shipping.entity.PickDetail; import org.cpte.modules.shipping.entity.PickDetail;
import org.cpte.modules.shipping.entity.Task; import org.cpte.modules.shipping.entity.Task;
import org.cpte.modules.shipping.mapper.PickDetailMapper; import org.cpte.modules.shipping.mapper.PickDetailMapper;
import org.cpte.modules.shipping.mapper.TaskMapper;
import org.cpte.modules.shipping.service.IPickDetailService; import org.cpte.modules.shipping.service.IPickDetailService;
import org.cpte.modules.shipping.service.ITaskService; import org.cpte.modules.shipping.service.ITaskService;
import org.cpte.modules.shipping.vo.AllocationData; import org.cpte.modules.shipping.vo.AllocationData;
@ -57,8 +56,6 @@ public class AllocateProcessor {
@Autowired @Autowired
private ItemKeyMapper itemKeyMapper; private ItemKeyMapper itemKeyMapper;
@Autowired @Autowired
private InventoryMapper inventoryMapper; private InventoryMapper inventoryMapper;
@ -347,6 +344,21 @@ public class AllocateProcessor {
// 智能排序库存 // 智能排序库存
List<InventoryScore> scoredInventories = scoreInventories(matchedInventories); List<InventoryScore> scoredInventories = scoreInventories(matchedInventories);
//指定容器出库
if (pickDetail.getStockId() != null) {
//根据容器分组
Map<Long, List<InventoryScore>> inventoryScoreMap = scoredInventories.stream().collect(Collectors.groupingBy(InventoryScore::getStockId));
scoredInventories = inventoryScoreMap.get(pickDetail.getStockId());
}
if (CollectionUtils.isEmpty(scoredInventories)) {
Stock stock = stockService.getById(pickDetail.getStockId());
String message = String.format("任务号【%s】,容器【%s】无库存", pick.getNo(), stock.getStockCode());
errorMsgSet.add(message);
return;
}
//未分配数量 //未分配数量
BigDecimal remainingQty = totalUnAllocatedQty; BigDecimal remainingQty = totalUnAllocatedQty;
for (InventoryScore inventoryScore : scoredInventories) { for (InventoryScore inventoryScore : scoredInventories) {
@ -354,6 +366,7 @@ public class AllocateProcessor {
if (remainingQty.compareTo(BigDecimal.ZERO) <= 0) { if (remainingQty.compareTo(BigDecimal.ZERO) <= 0) {
break; break;
} }
Inventory inventory = inventoryScore.getInventory(); Inventory inventory = inventoryScore.getInventory();
// 库存可用数量 // 库存可用数量
@ -432,7 +445,7 @@ public class AllocateProcessor {
double distanceScore = calculateClusterDistanceCost(currPoint, bestPoint) * 0.3; double distanceScore = calculateClusterDistanceCost(currPoint, bestPoint) * 0.3;
//当前巷道的库位数 //当前巷道的库位数
List<Point> points= pointMapper.findByColAndLayer(currPoint.getColNum(), currPoint.getLayerNum()); List<Point> points = pointMapper.findByColAndLayer(currPoint.getColNum(), currPoint.getLayerNum());
// 目标库位的深度位转换为索引 // 目标库位的深度位转换为索引
int targetIndex = Integer.parseInt(currPoint.getRowNum()) - 1; int targetIndex = Integer.parseInt(currPoint.getRowNum()) - 1;
@ -459,7 +472,7 @@ public class AllocateProcessor {
log.info("【{}】库位距离评分: {}-移位评分: {}-总得分: {}-移位次数: {}", log.info("【{}】库位距离评分: {}-移位评分: {}-总得分: {}-移位次数: {}",
currPoint.getPointCode(), distanceScore, moveScore, totalScore, movePoints.size()); currPoint.getPointCode(), distanceScore, moveScore, totalScore, movePoints.size());
return new InventoryScore(inventory, totalScore, bestPoint, movePoints); return new InventoryScore(inventory, inventory.getStockId(), totalScore, bestPoint, movePoints);
} }
/** /**
@ -716,16 +729,20 @@ public class AllocateProcessor {
Map<Long, Stock> stockMap = stockService.queryByStockIdsToMap(stockIds); Map<Long, Stock> stockMap = stockService.queryByStockIdsToMap(stockIds);
for (Inventory inv : moveInventoryList) { for (Inventory inv : moveInventoryList) {
Item moveItem = moveItemMap.get(inv.getItemId()); try {
Point fromPoint = fromPointMap.get(inv.getPointId()); Item moveItem = moveItemMap.get(inv.getItemId());
Stock stock = stockMap.get(inv.getStockId()); Point fromPoint = fromPointMap.get(inv.getPointId());
String taskNo = moveSerialNumberRule.generateSerialNumber(GeneralConstant.MOVE_ORDER_NO); Stock stock = stockMap.get(inv.getStockId());
//根据算法找到最优的目标库位 String taskNo = moveSerialNumberRule.generateSerialNumber(GeneralConstant.MOVE_ORDER_NO);
Point toPoint = allocatePoint(fromPoint, itemKeyMap.get(inv.getItemKeyId())); //根据算法找到最优的目标库位
Task moveTask = taskService.bulidTask(taskNo, TaskTypeEnum.MOVE.getValue(), moveItem, fromPoint, toPoint, stock, null, null, inv.getItemKeyId(), inv.getId(), inv.getQuantity(), 0); Point toPoint = allocatePoint(fromPoint, itemKeyMap.get(inv.getItemKeyId()));
moveList.add(moveTask); Task moveTask = taskService.bulidTask(taskNo, TaskTypeEnum.MOVE.getValue(), moveItem, fromPoint, toPoint, stock, null, null, inv.getItemKeyId(), inv.getId(), inv.getQuantity(), 0);
pointService.bindPoint(toPoint); moveList.add(moveTask);
log.info("生成移位任务:{}- 容器:{} - 库位:{} - 库存数量:{}", taskNo, stock.getStockCode(), fromPoint.getPointCode(), inv.getQuantity()); pointService.bindPoint(toPoint);
log.info("生成移位任务:{}- 容器:{} - 库位:{} - 库存数量:{}", taskNo, stock.getStockCode(), fromPoint.getPointCode(), inv.getQuantity());
} catch (Exception e) {
throw e;
}
} }
return moveList; return moveList;
} }
@ -740,7 +757,7 @@ public class AllocateProcessor {
Area area = areaMapper.selectById(currentPoint.getAreaId()); Area area = areaMapper.selectById(currentPoint.getAreaId());
String areaCode = area.getAreaCode(); String areaCode = area.getAreaCode();
List<ItemKey> itemKeyIds = Collections.singletonList(itemKey); List<ItemKey> itemKeyIds = Collections.singletonList(itemKey);
return scanTrayProcessor.allocatePoint(itemKeyIds, currentPoint, areaCode,BusinessTypeEnum.MOVE.getValue()); return scanTrayProcessor.allocatePoint(itemKeyIds, currentPoint, areaCode, BusinessTypeEnum.MOVE.getValue());
} }
/** /**

View File

@ -12,6 +12,8 @@ import java.util.List;
public class InventoryScore { public class InventoryScore {
// 库存 // 库存
private Inventory inventory; private Inventory inventory;
//容器
private Long stockId;
// 分数 // 分数
private double score; private double score;
//最佳出库口 //最佳出库口

View File

@ -51,7 +51,7 @@ spring:
jdbc: jdbc:
initialize-schema: embedded initialize-schema: embedded
#定时任务启动开关true-开 false-关 #定时任务启动开关true-开 false-关
auto-startup: true auto-startup: false
#延迟1秒启动定时任务 #延迟1秒启动定时任务
startup-delay: 1s startup-delay: 1s
#启动时更新己存在的Job #启动时更新己存在的Job
@ -145,7 +145,7 @@ spring:
selectWhereAlwayTrueCheck: false selectWhereAlwayTrueCheck: false
# 打开mergeSql功能慢SQL记录 # 打开mergeSql功能慢SQL记录
stat: stat:
log-slow-sql: true #log-slow-sql: false
slow-sql-millis: 5000 slow-sql-millis: 5000
merge-sql: true merge-sql: true
datasource: datasource:

2336
hs_err_pid20204.log 100644

File diff suppressed because it is too large Load Diff