no message
parent
d73d0d454f
commit
d8b84477ad
|
|
@ -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<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 库位评分计算
|
* 库位评分计算
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,13 @@ public interface IAsnService extends IService<Asn> {
|
||||||
*/
|
*/
|
||||||
void receiveAsn(Long asnId);
|
void receiveAsn(Long asnId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消操作
|
||||||
|
*
|
||||||
|
* @param asn 入库单
|
||||||
|
*/
|
||||||
|
void cancelAsn(Asn asn);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<>();
|
||||||
|
|
|
||||||
|
|
@ -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("操作成功!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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)必填");
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证起点
|
* 验证起点
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 行号
|
* 行号
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新出库单
|
* 刷新出库单
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
//最佳出库口
|
//最佳出库口
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue