no message

main
HUOJIN\92525 2025-12-14 23:40:22 +08:00
parent 74564b64ab
commit 981b83aa2d
52 changed files with 2934 additions and 1490 deletions

View File

@ -1,8 +1,6 @@
package org.cpte.modules.hikAgv.response;
package org.jeecg.common.api.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
@ -14,11 +12,6 @@ import java.io.Serializable;
* @email cpteos@163.com
* @date 2019119
*/
@JsonPropertyOrder({
"code",
"message",
"data"
})
@Data
public class HikResult implements Serializable {

View File

@ -1,4 +1,4 @@
package org.cpte.modules.tesAgv.response;
package org.jeecg.common.api.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

View File

@ -49,38 +49,25 @@ public class DictAspect {
private static final String JAVA_UTIL_DATE = "java.util.Date";
/**
* Pointcut
*/
/* @Pointcut("(@within(org.springframework.web.bind.annotation.RestController) || " +
"@within(org.springframework.stereotype.Controller) || @annotation(org.jeecg.common.aspect.annotation.AutoDict)) " +
"&& execution(public org.jeecg.common.api.vo.Result org.jeecg..*.*(..)) || execution(public * org.cpte..*.*Controller.*(..))")
public void excudeService() {
}*/
/**
* Pointcut
*/
@Pointcut("(@within(org.springframework.web.bind.annotation.RestController) || " +
"@within(org.springframework.stereotype.Controller) || " +
"@annotation(org.jeecg.common.aspect.annotation.AutoDict)) " +
"&& (execution(public org.jeecg.common.api.vo.Result org.jeecg..*.*(..)) || " +
"(execution(public * org.cpte..*.*Controller.*(..)) && " +
"!execution(public org.cpte.modules.hikAgv.response.* org.cpte.modules.hikAgv.controller.*.*(..)) && " +
"!execution(public org.cpte.modules.tesAgv.response.* org.cpte.modules.tesAgv.controller.*.*(..))))")
"@within(org.springframework.stereotype.Controller) || @annotation(org.jeecg.common.aspect.annotation.AutoDict)) " +
"&& execution(public org.jeecg.common.api.vo.Result org.jeecg..*.*(..))")
public void excudeService() {
}
@Around("excudeService()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long time1=System.currentTimeMillis();
long time1 = System.currentTimeMillis();
Object result = pjp.proceed();
long time2=System.currentTimeMillis();
log.debug("获取JSON数据 耗时:"+(time2-time1)+"ms");
long start=System.currentTimeMillis();
result=this.parseDictText(result);
long end=System.currentTimeMillis();
log.debug("注入字典到JSON数据 耗时"+(end-start)+"ms");
long time2 = System.currentTimeMillis();
log.debug("获取JSON数据 耗时:" + (time2 - time1) + "ms");
long start = System.currentTimeMillis();
result = this.parseDictText(result);
long end = System.currentTimeMillis();
log.debug("注入字典到JSON数据 耗时" + (end - start) + "ms");
return result;
}
@ -90,20 +77,21 @@ public class DictAspect {
* SysUser sex @Dict(dicCode = "sex") text listtext_dictText
* sex_dictText
* {
* sex:1,
* sex_dictText:"男"
* sex:1,
* sex_dictText:"男"
* }
* sext_dictTexttable
* customRender:function (text) {
* if(text==1){
* return "男";
* }else if(text==2){
* return "女";
* }else{
* return text;
* }
* }
* vuetable
* customRender:function (text) {
* if(text==1){
* return "男";
* }else if(text==2){
* return "女";
* }else{
* return text;
* }
* }
* vuetable
*
* @param result
*/
private Object parseDictText(Object result) {
@ -117,24 +105,24 @@ public class DictAspect {
// 字典数据列表, key = 字典codevalue=数据列表
Map<String, List<String>> dataListMap = new HashMap<>(5);
//取出结果集
List<Object> records=((IPage) ((Result) result).getResult()).getRecords();
List<Object> records = ((IPage) ((Result) result).getResult()).getRecords();
//update-begin--Author:zyf -- Date:20220606 ----for【VUEN-1230】 判断是否含有字典注解,没有注解返回-----
Boolean hasDict= checkHasDict(records);
if(!hasDict){
Boolean hasDict = checkHasDict(records);
if (!hasDict) {
return result;
}
log.debug(" __ 进入字典翻译切面 DictAspect —— " );
log.debug(" __ 进入字典翻译切面 DictAspect —— ");
//update-end--Author:zyf -- Date:20220606 ----for【VUEN-1230】 判断是否含有字典注解,没有注解返回-----
for (Object record : records) {
String json="{}";
String json = "{}";
try {
//update-begin--Author:zyf -- Date:20220531 ----for【issues/#3629】 DictAspect Jackson序列化报错-----
//解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat
json = objectMapper.writeValueAsString(record);
json = objectMapper.writeValueAsString(record);
//update-end--Author:zyf -- Date:20220531 ----for【issues/#3629】 DictAspect Jackson序列化报错-----
} catch (JsonProcessingException e) {
log.error("json解析失败"+e.getMessage(),e);
log.error("json解析失败" + e.getMessage(), e);
}
//update-begin--Author:scott -- Date:20211223 ----for【issues/3303】restcontroller返回json数据后key顺序错乱 -----
JSONObject item = JSONObject.parseObject(json, Feature.OrderedField);
@ -148,7 +136,7 @@ public class DictAspect {
if (oConvertUtils.isEmpty(value)) {
continue;
}
//update-end--Author:scott -- Date:20190603 ----for解决继承实体字段无法翻译问题------
//update-end--Author:scott -- Date:20190603 ----for解决继承实体字段无法翻译问题------
if (field.getAnnotation(Dict.class) != null) {
if (!dictFieldList.contains(field)) {
dictFieldList.add(field);
@ -172,8 +160,8 @@ public class DictAspect {
//date类型默认转换string格式化日期
//update-begin--Author:zyf -- Date:20220531 ----for【issues/#3629】 DictAspect Jackson序列化报错-----
//if (JAVA_UTIL_DATE.equals(field.getType().getName())&&field.getAnnotation(JsonFormat.class)==null&&item.get(field.getName())!=null){
//SimpleDateFormat aDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
//SimpleDateFormat aDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
//}
//update-end--Author:zyf -- Date:20220531 ----for【issues/#3629】 DictAspect Jackson序列化报错-----
}
@ -203,7 +191,7 @@ public class DictAspect {
String value = record.getString(field.getName());
if (oConvertUtils.isNotEmpty(value)) {
List<DictModel> dictModels = translText.get(fieldDictCode);
if(dictModels==null || dictModels.size()==0){
if (dictModels == null || dictModels.size() == 0) {
continue;
}
@ -242,6 +230,7 @@ public class DictAspect {
*
* 1. SQL
* 2. SQL
*
* @param dataListMap
* @return
*/
@ -309,13 +298,13 @@ public class DictAspect {
log.debug("translateDictFromTableByKeys.dictCode:" + dictCode);
log.debug("translateDictFromTableByKeys.values:" + values);
//update-begin---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
//update-begin---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
if(null == dataSource){
if (null == dataSource) {
dataSource = "";
}
//update-end---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
List<DictModel> texts = commonApi.translateDictFromTableByKeys(table, text, code, values, dataSource);
//update-end---author:chenrui ---date:20231221 for[issues/#5643]解决分布式下表字典跨库无法查询问题------------
log.debug("translateDictFromTableByKeys.result:" + texts);
@ -393,7 +382,8 @@ public class DictAspect {
}
/**
*
*
*
* @param code
* @param text
* @param table
@ -402,39 +392,39 @@ public class DictAspect {
*/
@Deprecated
private String translateDictValue(String code, String text, String table, String key) {
if(oConvertUtils.isEmpty(key)) {
return null;
}
StringBuffer textValue=new StringBuffer();
if (oConvertUtils.isEmpty(key)) {
return null;
}
StringBuffer textValue = new StringBuffer();
String[] keys = key.split(",");
for (String k : keys) {
String tmpValue = null;
log.debug(" 字典 key : "+ k);
log.debug(" 字典 key : " + k);
if (k.trim().length() == 0) {
continue; //跳过循环
}
//update-begin--Author:scott -- Date:20210531 ----for !56 优化微服务应用下存在表字段需要字典翻译时加载缓慢问题-----
if (!StringUtils.isEmpty(table)){
log.debug("--DictAspect------dicTable="+ table+" ,dicText= "+text+" ,dicCode="+code);
String keyString = String.format("sys:cache:dictTable::SimpleKey [%s,%s,%s,%s]",table,text,code,k.trim());
if (redisTemplate.hasKey(keyString)){
if (!StringUtils.isEmpty(table)) {
log.debug("--DictAspect------dicTable=" + table + " ,dicText= " + text + " ,dicCode=" + code);
String keyString = String.format("sys:cache:dictTable::SimpleKey [%s,%s,%s,%s]", table, text, code, k.trim());
if (redisTemplate.hasKey(keyString)) {
try {
tmpValue = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
} catch (Exception e) {
log.warn(e.getMessage());
}
}else {
tmpValue= commonApi.translateDictFromTable(table,text,code,k.trim());
} else {
tmpValue = commonApi.translateDictFromTable(table, text, code, k.trim());
}
}else {
String keyString = String.format("sys:cache:dict::%s:%s",code,k.trim());
if (redisTemplate.hasKey(keyString)){
} else {
String keyString = String.format("sys:cache:dict::%s:%s", code, k.trim());
if (redisTemplate.hasKey(keyString)) {
try {
tmpValue = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
} catch (Exception e) {
log.warn(e.getMessage());
log.warn(e.getMessage());
}
}else {
} else {
tmpValue = commonApi.translateDict(code, k.trim());
}
}
@ -453,11 +443,12 @@ public class DictAspect {
/**
* Dict
*
* @param records
* @return
*/
private Boolean checkHasDict(List<Object> records){
if(oConvertUtils.isNotEmpty(records) && records.size()>0){
private Boolean checkHasDict(List<Object> records) {
if (oConvertUtils.isNotEmpty(records) && records.size() > 0) {
for (Field field : oConvertUtils.getAllFields(records.get(0))) {
if (oConvertUtils.isNotEmpty(field.getAnnotation(Dict.class))) {
return true;

View File

@ -20,8 +20,8 @@ public interface AgvTaskMapper extends BaseMapper<AgvTask> {
* @param carrierCode
* @param agvVendor ;AGV/TES
*/
@Select(value = "select count(id) from data_agv_task where carrier_code = #{carrierCode} and status in (1,2,3) and agv_vendor = #{agvVendor} for update ")
Long existsByStockCode(@Param("carrierCode") String carrierCode, @Param("agvVendor") String agvVendor);
@Select(value = "select 1 from data_agv_task where carrier_code = #{carrierCode} and status in (1,2,3) and agv_vendor = #{agvVendor} LIMIT 1 ")
Integer existsByStockCode(@Param("carrierCode") String carrierCode, @Param("agvVendor") String agvVendor);
/**
* AGV
@ -29,8 +29,8 @@ public interface AgvTaskMapper extends BaseMapper<AgvTask> {
* @param startCode
* @param agvVendor ;AGV/TES
*/
@Select(value = "select count(id) from data_agv_task where start_code = #{startCode} and status in (2,3) and agv_vendor = #{agvVendor} for update ")
Long existsByStartCode(@Param("startCode") String startCode, @Param("agvVendor") String agvVendor);
@Select(value = "select 1 from data_agv_task where start_code = #{startCode} and status in (2,3) and agv_vendor = #{agvVendor} LIMIT 1 ")
Integer existsByStartCode(@Param("startCode") String startCode, @Param("agvVendor") String agvVendor);
/**
@ -39,8 +39,8 @@ public interface AgvTaskMapper extends BaseMapper<AgvTask> {
* @param endCode
* @param agvVendor ;AGV/TES
*/
@Select(value = "select count(id) from data_agv_task where end_code = #{endCode} and status in (2,3) and agv_vendor = #{agvVendor} for update ")
Long existsByEndCode(@Param("endCode") String endCode, @Param("agvVendor") String agvVendor);
@Select(value = "select 1 from data_agv_task where end_code = #{endCode} and status in (2,3) and agv_vendor = #{agvVendor} LIMIT 1 ")
Integer existsByEndCode(@Param("endCode") String endCode, @Param("agvVendor") String agvVendor);
/**
* AGV

View File

@ -0,0 +1,398 @@
package org.cpte.modules.conveyorLine.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.cpte.modules.agvTask.mapper.AgvTaskMapper;
import org.cpte.modules.agvTask.service.IAgvTaskService;
import org.cpte.modules.base.entity.Item;
import org.cpte.modules.base.entity.ItemKey;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.base.mapper.ItemKeyMapper;
import org.cpte.modules.base.mapper.ItemMapper;
import org.cpte.modules.base.mapper.PointMapper;
import org.cpte.modules.base.mapper.StockMapper;
import org.cpte.modules.base.service.IPointService;
import org.cpte.modules.constant.enums.*;
import org.cpte.modules.conveyorLine.request.ScanTrayRequest;
import org.cpte.modules.conveyorLine.vo.PointScore;
import org.cpte.modules.conveyorLine.vo.ScanTrayData;
import org.cpte.modules.inventory.entity.Inventory;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
/**
*
*/
@Service
@Slf4j
public class ScanTrayProcessor {
@Autowired
private ItemMapper itemMapper;
@Autowired
private StockMapper stockMapper;
@Autowired
private PointMapper pointMapper;
@Autowired
private AsnMapper asnMapper;
@Autowired
private AsnDetailMapper asnDetailMapper;
@Autowired
private ItemKeyMapper itemKeyMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private AgvTaskMapper agvTaskMapper;
@Autowired
private IPointService pointService;
@Autowired
private IAgvTaskService agvTaskService;
/**
*
*
* @param scanTrayRequest
*/
public void scanTray(ScanTrayRequest scanTrayRequest) {
// 1.参数校验
validateParams(scanTrayRequest);
//2.数据准备
ScanTrayData data = prepareScanTrayData(scanTrayRequest);
//3.验证托盘
validateTray(data);
// 4.智能分配库位
Point dstPoint = allocatePoint(data.getAsn().getOrderType(), data.getItemKeys(), data.getStation());
// 5.生成TES任务
processAgvTask(data.getAsn(), data.getStock(), data.getStation(), dstPoint);
}
/**
*
*
* @param scanTrayRequest
*/
private void validateParams(ScanTrayRequest scanTrayRequest) {
if (StringUtils.isBlank(scanTrayRequest.getStockCode())) {
throw new RuntimeException("托盘码不能为空");
}
if (StringUtils.isBlank(scanTrayRequest.getStation())) {
throw new RuntimeException("工作站不能为空");
}
}
/**
*
*
* @param scanTrayRequest
* @return ScanTrayData
*/
private ScanTrayData prepareScanTrayData(ScanTrayRequest scanTrayRequest) {
ScanTrayData data = new ScanTrayData();
//工作站
Point station = pointMapper.queryByPointCode(scanTrayRequest.getStation());
data.setStation(station);
//容器
Stock stock = stockMapper.queryByStockCode(scanTrayRequest.getStockCode());
data.setStock(stock);
List<AsnDetail> asnDetails = asnDetailMapper.queryByStockCode(stock.getId(), AsnStatusEnum.CREATED.getValue());
if (CollectionUtils.isNotEmpty(asnDetails)) {
//明细集合
data.setAsnDetails(asnDetails);
//入库单
Asn asn = asnMapper.selectById(asnDetails.get(0).getAsnId());
data.setAsn(asn);
//物料
Item item = itemMapper.selectById(asnDetails.get(0).getItemId());
data.setItem(item);
//项目号、任务号、批次号、外部库存状态
List<String> projectList = asnDetails.stream().map(AsnDetail::getProject).filter(StringUtils::isNotBlank).distinct().toList();
List<String> taskNoList = asnDetails.stream().map(AsnDetail::getTaskNo).filter(StringUtils::isNotBlank).distinct().toList();
List<String> propC1List = asnDetails.stream().map(AsnDetail::getPropC1).filter(StringUtils::isNotBlank).distinct().toList();
List<String> propC3List = asnDetails.stream().map(AsnDetail::getPropC3).filter(StringUtils::isNotBlank).distinct().toList();
List<ItemKey> itemKeys = itemKeyMapper.queryItemKeys(Collections.singletonList(item.getId()), Collections.singletonList(asn.getWhCode()), projectList, taskNoList, propC1List, propC3List);
data.setItemKeys(itemKeys);
}
return data;
}
/**
*
*
* @param data
*/
private void validateTray(ScanTrayData data) {
Long stockId = data.getStock().getId();
String stockCode = data.getStock().getStockCode();
List<AsnDetail> asnDetails = asnDetailMapper.queryByStockCode(stockId, AsnStatusEnum.CREATED.getValue());
if (CollectionUtils.isEmpty(asnDetails)) {
throw new RuntimeException("【" + stockCode + "】托盘,无入库任务");
}
//验证托盘是否有库存
if (inventoryMapper.exitsStockInventory(stockId) != null) {
throw new RuntimeException("【" + stockCode + "】托盘已入库");
}
//验证当前托盘是否生成了TES任务
if (agvTaskMapper.existsByStockCode(stockCode, AgvVendorEnum.TES.getValue()) > 0) {
throw new RuntimeException("【" + stockCode + "】托盘已扫描,请勿重复扫描");
}
}
/**
*
*
* @param orderType
* @param itemKeys
* @param station
* @return
*/
private Point allocatePoint(Integer orderType, List<ItemKey> itemKeys, Point station) {
String areaCode = "";
if (Set.of(0, 1, 2, 3).contains(orderType)) {
areaCode = AreaTypeEnum.CPCCQ.getValue();
} else {
areaCode = AreaTypeEnum.MJCCQ.getValue();
}
//1.优先寻找同物料/同仓库/同项目号/同任务号/同批次/同外部库存状态
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);
}
List<PointScore> scoredPoints = availablePoints.stream()
.map(point -> clusterPointScore(point, station, itemKeys))
.sorted(Comparator.comparing(PointScore::getScore).reversed())
.toList();
if (!scoredPoints.isEmpty()) {
return scoredPoints.get(0).getPoint();
}
throw new RuntimeException("系统无可用库位");
}
/**
*
*/
private PointScore clusterPointScore(Point point, Point station, List<ItemKey> itemKeys) {
double totalScore;
// 1. 距离评分 - 考虑从入库口到库位的距离
double distanceScore = calculateClusterDistanceCost(point, station) * 0.3;// 权重30%
// 2. 通道深度策略评分 - 新增核心策略
double channelDepthScore = calculateChannelDepthScore(point) * 0.25; // 权重25%
// 3. 通道类型评分 - 双通道优先
double channelScore = calculateChannelScore(point) * 0.15; // 权重15%
// 4. 均衡评分 - 避免热点区域
double balanceScore = calculateBalanceScore(point) * 0.1; // 权重10%
// 5. 物料聚集潜力评分 - 新增
double clusterPotentialScore = calculateClusterPotentialScore(point, itemKeys) * 0.2; // 权重20%
totalScore = distanceScore + channelDepthScore + channelScore + balanceScore + clusterPotentialScore;
log.info("【{}】库位总分:{} - 距离评分: {} - 通道深度策略评分: {} - 通道类型评分: {} - 均衡评分: {} - 物料聚集评分: {}", point.getPointCode(), totalScore, distanceScore, channelDepthScore, channelScore, balanceScore, clusterPotentialScore);
return new PointScore(point, totalScore);
}
/**
* -
* (04)
* (07)
*/
private double calculateChannelDepthScore(Point point) {
if (point.getIzDoubleLane().equals(1)) {
// 双通道策略:中间优先,得分 04 > 03=05 > 02=06 > 01=07
return doubleChannelDepthScore(Integer.parseInt(point.getRowNum()));
} else {
// 单通道策略:深度优先,得分 07 > 06 > 05 > 04 > 03 > 02 > 01
return singleChannelDepthScore(Integer.parseInt(point.getRowNum()));
}
}
/**
*
* 04 > 03=05 > 02=06 > 01=07
*/
private double doubleChannelDepthScore(int depth) {
return switch (depth) {
case 4 -> 100; // 最优位置
case 3, 5 -> 85; // 次优位置
case 2, 6 -> 70; // 良好位置
case 1, 7 -> 60; // 一般位置
default -> 50;
};
}
/**
*
* 07 > 06 > 05 > 04 > 03 > 02 > 01
*/
private double singleChannelDepthScore(int depth) {
// 深度越大分数越高07得100分01得14分
return depth * 100.0 / 7;
}
/**
*
*
*/
private double calculateChannelScore(Point point) {
return point.getIzDoubleLane().equals(1) ? 100 : 60;
}
/**
*
*
*/
private double calculateBalanceScore(Point point) {
// 查询同一巷道相邻库位的占用情况
int sameColumnOccupied = pointMapper.countOccupiedInSameColumn(point.getColNum(), point.getLayerNum());
// 占用率越低,分数越高
double occupancyRate = sameColumnOccupied / 7.0; // 7个深度位置
return (1 - occupancyRate) * 100;
}
/**
*
*
*
*/
private double calculateClusterPotentialScore(Point point, List<ItemKey> itemKeys) {
double score = 0.0;
// 检查同一巷道相邻库位
int minDepth, maxDepth;
if (point.getIzDoubleLane().equals(1)) {
// 双通道考虑整个巷道所有位置1-7
minDepth = 1;
maxDepth = 7;
} else {
// 单通道:只考虑同一侧的位置(从当前深度向前)
minDepth = Integer.parseInt(point.getRowNum());
maxDepth = 7;
}
List<Inventory> neighbors = inventoryMapper.findNeighborPoints(
point.getColNum(), point.getLayerNum(), String.valueOf(minDepth), String.valueOf(maxDepth));
if (CollectionUtils.isEmpty(neighbors)) {
return score;
}
List<Long> itemKeyIds = neighbors.stream().map(Inventory::getItemKeyId).distinct().toList();
List<ItemKey> exitItemKeys = itemKeyMapper.queryItemKeyByIds(itemKeyIds);
// 为每个ItemKey计算匹配度并累加分数
for (ItemKey itemKey : itemKeys) {
Long itemId = itemKey.getItemId();
String whCode = itemKey.getWhCode();
String project = itemKey.getProject();
String taskNo = itemKey.getTaskNo();
String propC1 = itemKey.getPropC1();
String propC3 = itemKey.getPropC3();
for (ItemKey neighbor : exitItemKeys) {
// 同SKU加分
if (neighbor.getItemId().equals(itemId)) {
score += 20;
// 同仓库代码加分
if (whCode != null && whCode.equals(neighbor.getWhCode())) {
score += 15;
}
// 同批次号加分
if (StringUtils.isNotBlank(propC1) &&
StringUtils.isNotBlank(neighbor.getPropC1()) &&
propC1.equals(neighbor.getPropC1())) {
score += 10;
}
// 同项目号加分
if (StringUtils.isNotBlank(project) &&
StringUtils.isNotBlank(neighbor.getProject()) &&
project.equals(neighbor.getProject())) {
score += 10;
}
// 同任务号加分
if (StringUtils.isNotBlank(taskNo) &&
StringUtils.isNotBlank(neighbor.getTaskNo()) &&
taskNo.equals(neighbor.getTaskNo())) {
score += 10;
}
// 同外部库存状态加分
if (StringUtils.isNotBlank(propC3) &&
StringUtils.isNotBlank(neighbor.getPropC3()) &&
propC3.equals(neighbor.getPropC3())) {
score += 5;
}
}
}
}
// 限制最高分
return Math.min(score, 100);
}
/**
*
*
*/
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));
}
@Transactional(rollbackFor = Exception.class)
public void processAgvTask(Asn asn, Stock stock, Point station, Point dstPoint) {
//锁定目标库位
pointService.bindPoint(dstPoint);
//验证通过生成Tes任务
agvTaskService.createAgvTask(asn.getId(), stock.getStockCode(), station.getPointCode(), dstPoint.getPointCode(), null, BusinessTypeEnum.INBOUND.getValue(), 0, AgvVendorEnum.TES.getValue());
}
}

View File

@ -6,51 +6,22 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.cpte.modules.agvTask.entity.AgvTask;
import org.cpte.modules.agvTask.mapper.AgvTaskMapper;
import org.cpte.modules.agvTask.service.IAgvTaskService;
import org.cpte.modules.base.entity.ItemKey;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.base.mapper.ItemKeyMapper;
import org.cpte.modules.base.mapper.PointMapper;
import org.cpte.modules.base.mapper.StockMapper;
import org.cpte.modules.base.service.IPointService;
import org.cpte.modules.constant.enums.*;
import org.cpte.modules.conveyorLine.request.ScanTrayRequest;
import org.cpte.modules.conveyorLine.service.IConveyorLineService;
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.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.conveyorLine.service.ScanTrayProcessor;
import org.cpte.modules.shipping.entity.Task;
import org.cpte.modules.shipping.mapper.TaskMapper;
import org.cpte.modules.utils.RedisDistributedLockUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
@Service
@Slf4j
public class IConveyorLineServiceImpl implements IConveyorLineService {
@Autowired
private PointMapper pointMapper;
@Autowired
private StockMapper stockMapper;
@Autowired
private AsnMapper asnMapper;
@Autowired
private AsnDetailMapper asnDetailMapper;
@Autowired
private TaskMapper taskMapper;
@ -58,296 +29,27 @@ public class IConveyorLineServiceImpl implements IConveyorLineService {
private AgvTaskMapper agvTaskMapper;
@Autowired
private ItemKeyMapper itemKeyMapper;
private ScanTrayProcessor scanTrayProcessor;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private IPointService pointService;
@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("工作站不能为空");
}
}
private RedisDistributedLockUtil redissonLock;
@Override
@Transactional(rollbackFor = Exception.class)
public void scanTray(ScanTrayRequest scanTrayRequest) {
//验证参数
validateParams(scanTrayRequest);
//工作站
Point station = pointMapper.queryByPointCode(scanTrayRequest.getStation());
//容器
Stock stock = stockMapper.queryByStockCode(scanTrayRequest.getStockCode());
//验证入库信息
List<AsnDetail> asnDetails = asnDetailMapper.queryByStockCode(stock.getId(), AsnStatusEnum.CREATED.getValue());
if (CollectionUtils.isEmpty(asnDetails)) {
throw new RuntimeException("【" + scanTrayRequest.getStockCode() + "】托盘,无入库任务");
}
Asn asn = asnMapper.selectById(asnDetails.get(0).getAsnId());
AsnDetail asnDetail = asnDetails.get(0);
//验证托盘是否有库存
if (inventoryMapper.queryByStockId(stock.getId()) != null) {
throw new RuntimeException("【" + scanTrayRequest.getStockCode() + "】托盘已入库");
}
//验证当前托盘是否生成了TES任务
if (agvTaskMapper.existsByStockCode(stock.getStockCode(), AgvVendorEnum.TES.getValue()) > 0) {
throw new RuntimeException("【" + scanTrayRequest.getStockCode() + "】托盘已扫描,请勿重复扫描");
}
//项目号、任务号、批次号、外部库存状态
List<String> projectList = asnDetails.stream().map(AsnDetail::getProject).filter(StringUtils::isNotBlank).distinct().toList();
List<String> taskNoList = asnDetails.stream().map(AsnDetail::getTaskNo).filter(StringUtils::isNotBlank).distinct().toList();
List<String> propC1List = asnDetails.stream().map(AsnDetail::getPropC1).filter(StringUtils::isNotBlank).distinct().toList();
List<String> propC3List = asnDetails.stream().map(AsnDetail::getPropC3).filter(StringUtils::isNotBlank).distinct().toList();
List<ItemKey> itemKeys = itemKeyMapper.queryItemKeys(Collections.singletonList(asnDetail.getItemId()), Collections.singletonList(asn.getWhCode()), projectList, taskNoList, propC1List, propC3List);
//通过算法获取目标点位
Point dstPoint = allocatePoint(asn.getOrderType(), itemKeys, station);
//锁定目标库位
pointService.bindPoint(dstPoint);
//验证通过生成Tes任务
iAgvTaskService.createAgvTask(asn.getId(), stock.getStockCode(), station.getPointCode(), dstPoint.getPointCode(), null, BusinessTypeEnum.INBOUND.getValue(), 0, AgvVendorEnum.TES.getValue());
}
/**
*
*
* @param orderType
* @param itemKeys
* @param station
* @return
*/
private Point allocatePoint(Integer orderType, List<ItemKey> itemKeys, Point station) {
String areaCode = "";
if (Set.of(0, 1, 2, 3).contains(orderType)) {
areaCode = AreaTypeEnum.CPCCQ.getValue();
} else {
areaCode = AreaTypeEnum.MJCCQ.getValue();
}
//1.优先寻找同物料/同仓库/同项目号/同任务号/同批次/同外部库存状态
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);
}
List<PointScore> scoredPoints = availablePoints.stream()
.map(point -> clusterPointScore(point, station, itemKeys))
.sorted(Comparator.comparing(PointScore::getScore).reversed())
.toList();
if (!scoredPoints.isEmpty()) {
return scoredPoints.get(0).getPoint();
}
throw new RuntimeException("系统无可用库位");
}
/**
*
*
*/
/**
*
*/
private PointScore clusterPointScore(Point point, Point station, List<ItemKey> itemKeys) {
double totalScore;
// 1. 距离评分 - 考虑从入库口到库位的距离
double distanceScore = calculateClusterDistanceCost(point, station) * 0.3;// 权重30%
// 2. 通道深度策略评分 - 新增核心策略
double channelDepthScore = calculateChannelDepthScore(point) * 0.25; // 权重25%
// 3. 通道类型评分 - 双通道优先
double channelScore = calculateChannelScore(point) * 0.15; // 权重15%
// 4. 均衡评分 - 避免热点区域
double balanceScore = calculateBalanceScore(point) * 0.1; // 权重10%
// 5. 物料聚集潜力评分 - 新增
double clusterPotentialScore = calculateClusterPotentialScore(point, itemKeys) * 0.2; // 权重20%
totalScore = distanceScore + channelDepthScore + channelScore + balanceScore + clusterPotentialScore;
log.info("【{}】库位总分:{} - 距离评分: {} - 通道深度策略评分: {} - 通道类型评分: {} - 均衡评分: {} - 物料聚集评分: {}", point.getPointCode(), totalScore, distanceScore, channelDepthScore, channelScore, balanceScore, clusterPotentialScore);
return new PointScore(point, totalScore);
}
/**
* -
* (04)
* (07)
*/
private double calculateChannelDepthScore(Point point) {
if (point.getIzDoubleLane().equals(1)) {
// 双通道策略:中间优先,得分 04 > 03=05 > 02=06 > 01=07
return doubleChannelDepthScore(Integer.parseInt(point.getRowNum()));
} else {
// 单通道策略:深度优先,得分 07 > 06 > 05 > 04 > 03 > 02 > 01
return singleChannelDepthScore(Integer.parseInt(point.getRowNum()));
}
}
/**
*
* 04 > 03=05 > 02=06 > 01=07
*/
private double doubleChannelDepthScore(int depth) {
return switch (depth) {
case 4 -> 100; // 最优位置
case 3, 5 -> 85; // 次优位置
case 2, 6 -> 70; // 良好位置
case 1, 7 -> 60; // 一般位置
default -> 50;
};
}
/**
*
* 07 > 06 > 05 > 04 > 03 > 02 > 01
*/
private double singleChannelDepthScore(int depth) {
// 深度越大分数越高07得100分01得14分
return depth * 100.0 / 7;
}
/**
*
*
*/
private double calculateChannelScore(Point point) {
return point.getIzDoubleLane().equals(1) ? 100 : 60;
}
/**
*
*
*/
private double calculateBalanceScore(Point point) {
// 查询同一巷道相邻库位的占用情况
int sameColumnOccupied = pointMapper.countOccupiedInSameColumn(point.getColNum(), point.getLayerNum());
// 占用率越低,分数越高
double occupancyRate = sameColumnOccupied / 7.0; // 7个深度位置
return (1 - occupancyRate) * 100;
}
/**
*
*
*
*/
/**
*
*
*
*/
private double calculateClusterPotentialScore(Point point, List<ItemKey> itemKeys) {
double score = 0.0;
// 检查同一巷道相邻库位
int minDepth, maxDepth;
if (point.getIzDoubleLane().equals(1)) {
// 双通道考虑整个巷道所有位置1-7
minDepth = 1;
maxDepth = 7;
} else {
// 单通道:只考虑同一侧的位置(从当前深度向前)
minDepth = Integer.parseInt(point.getRowNum());
maxDepth = 7;
}
List<Inventory> neighbors = inventoryMapper.findNeighborPoints(
point.getColNum(), point.getLayerNum(), String.valueOf(minDepth), String.valueOf(maxDepth));
if (CollectionUtils.isEmpty(neighbors)) {
return score;
}
List<Long> itemKeyIds = neighbors.stream().map(Inventory::getItemKeyId).distinct().toList();
List<ItemKey> exitItemKeys = itemKeyMapper.queryItemKeyByIds(itemKeyIds);
// 为每个ItemKey计算匹配度并累加分数
for (ItemKey itemKey : itemKeys) {
Long itemId = itemKey.getItemId();
String whCode = itemKey.getWhCode();
String project = itemKey.getProject();
String taskNo = itemKey.getTaskNo();
String propC1 = itemKey.getPropC1();
String propC3 = itemKey.getPropC3();
for (ItemKey neighbor : exitItemKeys) {
// 同SKU加分
if (neighbor.getItemId().equals(itemId)) {
score += 20;
// 同仓库代码加分
if (whCode != null && whCode.equals(neighbor.getWhCode())) {
score += 15;
}
// 同批次号加分
if (StringUtils.isNotBlank(propC1) &&
StringUtils.isNotBlank(neighbor.getPropC1()) &&
propC1.equals(neighbor.getPropC1())) {
score += 10;
}
// 同项目号加分
if (StringUtils.isNotBlank(project) &&
StringUtils.isNotBlank(neighbor.getProject()) &&
project.equals(neighbor.getProject())) {
score += 10;
}
// 同任务号加分
if (StringUtils.isNotBlank(taskNo) &&
StringUtils.isNotBlank(neighbor.getTaskNo()) &&
taskNo.equals(neighbor.getTaskNo())) {
score += 10;
}
// 同外部库存状态加分
if (StringUtils.isNotBlank(propC3) &&
StringUtils.isNotBlank(neighbor.getPropC3()) &&
propC3.equals(neighbor.getPropC3())) {
score += 5;
}
}
String lockKey = "scanTray:" + scanTrayRequest.getStockCode();
String lockValue = null;
try {
lockValue = redissonLock.tryLock(lockKey, 10);
if (StringUtils.isEmpty(lockValue)) {
throw new RuntimeException("扫描处理中,请稍后重试");
}
scanTrayProcessor.scanTray(scanTrayRequest);
} finally {
if (StringUtils.isNotEmpty(lockValue)) {
redissonLock.unlock(lockKey, lockValue);
}
}
// 限制最高分
return Math.min(score, 100);
}
/**
*
*
*/
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

View File

@ -0,0 +1,26 @@
package org.cpte.modules.conveyorLine.vo;
import lombok.Data;
import org.cpte.modules.base.entity.Item;
import org.cpte.modules.base.entity.ItemKey;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.inventory.entity.Inventory;
import org.cpte.modules.receive.entity.Asn;
import org.cpte.modules.receive.entity.AsnDetail;
import org.cpte.modules.shipping.entity.Pick;
import org.cpte.modules.shipping.entity.PickDetail;
import org.cpte.modules.shipping.vo.ItemGroupKey;
import java.util.List;
import java.util.Map;
@Data
public class ScanTrayData {
private Item item;
private Point station;
private Stock stock;
private Asn asn;
private List<AsnDetail> asnDetails;
private List<ItemKey> itemKeys;
}

View File

@ -10,9 +10,7 @@ import org.cpte.modules.hikAgv.request.CancelRequest;
import org.cpte.modules.hikAgv.request.SubmitRequest;
import org.cpte.modules.hikAgv.request.TaskReporterRequest;
import org.cpte.modules.hikAgv.service.IHikAgvService;
import org.cpte.modules.tesAgv.response.TesResult;
import org.jeecg.common.api.vo.Result;
import org.cpte.modules.hikAgv.response.HikResult;
import org.jeecg.common.api.vo.HikResult;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.config.shiro.IgnoreAuth;
import org.springframework.beans.factory.annotation.Autowired;

View File

@ -240,8 +240,7 @@ public class IHikAgvServiceImpl implements IHikAgvService {
* @param agvTask
*/
private void handleResend(AgvTask agvTask) {
Long count = agvTaskMapper.existsByStockCode(agvTask.getCarrierCode(), AgvVendorEnum.HIK.getValue());
if (count > 0) {
if (agvTaskMapper.existsByStockCode(agvTask.getCarrierCode(), AgvVendorEnum.HIK.getValue()) !=null) {
throw new RuntimeException("任务已重新生成,请勿重复操作! ");
}
AgvTask newAgvTask = iAgvTaskService.createAgvTask(agvTask.getBusinessDetailId(), agvTask.getCarrierCode(), agvTask.getStartCode(), agvTask.getEndCode(), null, agvTask.getType(),agvTask.getIzAll(), AgvVendorEnum.HIK.getValue());

View File

@ -23,8 +23,8 @@ public interface InventoryMapper extends BaseMapper<Inventory> {
* @param stockId
* @return
*/
@Select("select * from data_inventory where stock_id = #{stockId} and quantity>0 for update")
Inventory queryByStockId(@Param("stockId") Long stockId);
@Select("select 1 from data_inventory where stock_id = #{stockId} and quantity>0 LIMIT 1 ")
Integer exitsStockInventory(@Param("stockId") Long stockId);
// 查询相邻库位同一巷道深度±3范围内
@Select("SELECT di.* " +

View File

@ -3,7 +3,6 @@ package org.cpte.modules.quartz.job;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.cpte.modules.shipping.mapper.PickMapper;
import org.cpte.modules.shipping.mapper.TaskMapper;
import org.cpte.modules.shipping.service.IPickService;
import org.cpte.modules.shipping.service.ITaskService;
import org.jeecg.common.constant.CommonConstant;
@ -28,10 +27,7 @@ public class AllocateJob implements Job {
private PickMapper pickMapper;
@Autowired
private TaskMapper taskMapper;
@Autowired
private IPickService iPickService;
private IPickService pickService;
@Autowired
private ITaskService iTaskService;
@ -52,7 +48,7 @@ public class AllocateJob implements Job {
if (CollectionUtils.isNotEmpty(pickList)) {
// 分配出库
long startTime = System.currentTimeMillis();
List<String> resultMsg = iPickService.allocatePick(pickList);
List<String> resultMsg = pickService.allocatePick(pickList);
long endTime = System.currentTimeMillis();
log.info("分配出库明细耗时:{}ms", endTime - startTime);
if (CollectionUtils.isNotEmpty(resultMsg)) {

View File

@ -32,7 +32,7 @@ public class HikAgvJob implements Job {
String taskSubmitUrl = "http://localhost:8000/cpte-wms/rcs/rtas/api/robot/controller/task/submit";
for (AgvTask agvTask : agvTaskList) {
if(BusinessTypeEnum.INBOUND.getValue().equals(agvTask.getType())){
boolean isStartCodeAvailable = agvTaskMapper.existsByStartCode(agvTask.getStartCode(), AgvVendorEnum.HIK.getValue()) == 0;
boolean isStartCodeAvailable = agvTaskMapper.existsByStartCode(agvTask.getStartCode(), AgvVendorEnum.HIK.getValue()) ==null ;
if (isStartCodeAvailable) {
hikAgvService.sendHikAgvTask(
taskSubmitUrl,
@ -41,7 +41,7 @@ public class HikAgvJob implements Job {
);
}
}else if(BusinessTypeEnum.OUTBOUND.getValue().equals(agvTask.getType())){
boolean isEndCodeAvailable = agvTaskMapper.existsByEndCode(agvTask.getEndCode(), AgvVendorEnum.HIK.getValue()) == 0;
boolean isEndCodeAvailable = agvTaskMapper.existsByEndCode(agvTask.getEndCode(), AgvVendorEnum.HIK.getValue()) == null;
if (isEndCodeAvailable) {
hikAgvService.sendHikAgvTask(
taskSubmitUrl,

View File

@ -40,7 +40,7 @@ public interface AsnDetailMapper extends BaseMapper<AsnDetail> {
* @param status
* @return AsnDetail
*/
@Select("select * from data_asn_detail where stock_id = #{stockId} and status = #{status} for update")
@Select("select * from data_asn_detail where stock_id = #{stockId} and status = #{status} ")
List<AsnDetail> queryByStockCode(@Param("stockId") Long stockId, @Param("status") Integer status);
@Select("select MAX(line_no) from data_asn_detail where asn_id = #{asnId} ")

View File

@ -17,7 +17,7 @@ public interface AsnMapper extends BaseMapper<Asn> {
* @param no
* @return Asn
*/
@Select("select * from data_asn where no = #{no} for update ")
@Select("select * from data_asn where no = #{no} ")
Asn queryByNo(@Param("no") String no);

View File

@ -1,13 +1,18 @@
package org.cpte.modules.receive.service;
import org.cpte.modules.base.entity.Item;
import org.cpte.modules.base.entity.ItemKey;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.receive.entity.Asn;
import org.cpte.modules.receive.entity.AsnDetail;
import com.baomidou.mybatisplus.extension.service.IService;
import org.cpte.modules.receive.entity.ReceiveRecord;
import org.cpte.modules.saiWms.request.InboundRequest;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
* @Description:
@ -24,4 +29,25 @@ public interface IAsnDetailService extends IService<AsnDetail> {
* @return List<AsnDetail>
*/
public List<AsnDetail> selectByMainId(Long mainId);
/**
*
*
* @param asn
* @param asnDetails
*/
void refreshAsn(Asn asn, List<AsnDetail> asnDetails);
/**
*
*
* @param asnDetail
* @param receivedQty
* @param itemKey
* @param dstPointId ID
* @return ReceiveRecord
*/
ReceiveRecord buildReceiveRecord(AsnDetail asnDetail, BigDecimal receivedQty, ItemKey itemKey, Long dstPointId);
}

View File

@ -1,14 +1,17 @@
package org.cpte.modules.receive.service;
import org.cpte.modules.base.entity.Item;
import org.cpte.modules.base.entity.ItemKey;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.receive.entity.AsnDetail;
import org.cpte.modules.receive.entity.Asn;
import com.baomidou.mybatisplus.extension.service.IService;
import org.cpte.modules.receive.entity.ReceiveRecord;
import org.cpte.modules.saiWms.request.InboundRequest;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -59,7 +62,7 @@ public interface IAsnService extends IService<Asn> {
*/
Asn buildAsn(InboundRequest inboundRequest);
/**
/**
*
*
* @param inboundDetails
@ -71,14 +74,13 @@ public interface IAsnService extends IService<Asn> {
*/
List<AsnDetail> buildAsnDetail(List<InboundRequest.InboundDetail> inboundDetails, Map<String, Item> itemMap, Stock stock, Point srcPoint, Point dstPoint);
/**
*
*
* @param asnId ID
* @param pointCode
*/
void receiveGoods(Long asnId, String pointCode);
void receiveAsn(Long asnId, String pointCode);
/**
*

View File

@ -0,0 +1,241 @@
package org.cpte.modules.receive.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.cpte.modules.base.entity.ItemKey;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.base.mapper.PointMapper;
import org.cpte.modules.base.mapper.StockMapper;
import org.cpte.modules.base.service.IItemKeyService;
import org.cpte.modules.base.service.IPointService;
import org.cpte.modules.base.service.IStockService;
import org.cpte.modules.constant.enums.AsnStatusEnum;
import org.cpte.modules.inventory.entity.Inventory;
import org.cpte.modules.inventory.mapper.InventoryMapper;
import org.cpte.modules.inventory.service.IInventoryService;
import org.cpte.modules.inventoryLog.entity.InventoryLog;
import org.cpte.modules.inventoryLog.service.IInventoryLogService;
import org.cpte.modules.receive.entity.Asn;
import org.cpte.modules.receive.entity.AsnDetail;
import org.cpte.modules.receive.entity.ReceiveRecord;
import org.cpte.modules.receive.mapper.AsnDetailMapper;
import org.cpte.modules.receive.mapper.AsnMapper;
import org.cpte.modules.receive.vo.ReceiveData;
import org.cpte.modules.shipping.entity.Pick;
import org.cpte.modules.shipping.vo.AllocationData;
import org.cpte.modules.utils.BatchUtil;
import org.cpte.modules.utils.BigDecimalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
*
*/
@Service
@Slf4j
public class ReceiveProcessor {
@Autowired
private StockMapper stockMapper;
@Autowired
private PointMapper pointMapper;
@Autowired
private AsnMapper asnMapper;
@Autowired
private AsnDetailMapper asnDetailMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private IStockService stockService;
@Autowired
private IPointService pointService;
@Autowired
private IAsnDetailService asnDetailService;
@Autowired
private IItemKeyService itemKeyService;
@Autowired
private IInventoryService inventoryService;
@Autowired
private IInventoryLogService inventoryLogService;
@Autowired
private BatchUtil batchUtil;
/**
*
*
* @param asnId
* @param dstPointCode
*/
public void receiveAsn(Long asnId, String dstPointCode) {
// 1.数据准备
ReceiveData data = prepareReceiveData(asnId, dstPointCode);
//2.验证托盘
// validateStock(data.getStock());
//3.创建数据结构
List<AsnDetail> updateToAsnDetail = new ArrayList<>();
List<ReceiveRecord> createToReceiveRecord = new ArrayList<>();
List<Inventory> createToInventory = new ArrayList<>();
List<InventoryLog> createToInventoryLog = new ArrayList<>();
//4.处理数据
receive(data, updateToAsnDetail, createToReceiveRecord, createToInventory, createToInventoryLog);
//5.批量操作
batchOperation(updateToAsnDetail, createToReceiveRecord, createToInventory, createToInventoryLog);
//6.刷新入库
refreshData(data);
//7.更新容器状态和位置
updateStockAndPoint(data.getStock(), data.getDstPoint());
//8.回传
}
/**
*
*
* @param asnId
* @param dstPointCode
* @return ReceiveData
*/
private ReceiveData prepareReceiveData(Long asnId, String dstPointCode) {
ReceiveData data = new ReceiveData();
Asn asn = asnMapper.selectById(asnId);
data.setAsn(asn);
List<AsnDetail> asnDetails = asnDetailMapper.selectByMainId(asnId);
if (CollectionUtils.isNotEmpty(asnDetails)) {
data.setAsnDetails(asnDetails);
Stock stock = stockMapper.selectById(asnDetails.get(0).getStockId());
data.setStock(stock);
}
Point dstPoint = pointMapper.queryByPointCode(dstPointCode);
data.setDstPoint(dstPoint);
return data;
}
/**
*
*
* @param stock
*/
private void validateStock(Stock stock) {
if (inventoryMapper.exitsStockInventory(stock.getId()) != null) {
throw new RuntimeException("【" + stock.getStockCode() + "】托盘已入库");
}
}
/**
*
*
* @param data
* @param updateToAsnDetail
* @param createToReceiveRecord
* @param createToInventory
* @param createToInventoryLog
*/
private void receive(ReceiveData data,
List<AsnDetail> updateToAsnDetail,
List<ReceiveRecord> createToReceiveRecord,
List<Inventory> createToInventory,
List<InventoryLog> createToInventoryLog) {
Asn asn = data.getAsn();
Point dstPoint = data.getDstPoint();
Stock stock = data.getStock();
for (AsnDetail asnDetail : data.getAsnDetails()) {
BigDecimal receivedQty = BigDecimalUtil.add(asnDetail.getReceivedQty(), asnDetail.getOrderQty(), 0);
asnDetail.setReceivedQty(receivedQty);
//更新明细状态
if (receivedQty.compareTo(asnDetail.getOrderQty()) >= 0) {
asnDetail.setStatus(AsnStatusEnum.RECEIVED.getValue());
} else {
asnDetail.setStatus(AsnStatusEnum.RECEIVING.getValue());
}
updateToAsnDetail.add(asnDetail);
//获取itemKey
ItemKey itemKey = itemKeyService.createItemKey(asnDetail.getItemId(), asn.getWhCode(), asnDetail.getProject(), asnDetail.getTaskNo(), asnDetail.getPropC1(), asnDetail.getPropC3());
//生成入库记录
ReceiveRecord receiveRecord = asnDetailService.buildReceiveRecord(asnDetail, receivedQty, itemKey, dstPoint.getId());
createToReceiveRecord.add(receiveRecord);
// 生成库存
Inventory inventory = inventoryService.buildInventory(stock.getId(), receivedQty, asn, receiveRecord);
createToInventory.add(inventory);
//添加库存日志
InventoryLog inventoryLog = inventoryLogService.buildInboundInventoryLog(inventory, asnDetail.getToPointId(), receivedQty, asn.getOrderNo(), asnDetail.getId(), asnDetail.getDescription());
createToInventoryLog.add(inventoryLog);
}
}
/**
*
*
* @param updateToAsnDetail
* @param createToReceiveRecord
* @param createToInventory
* @param createToInventoryLog
*/
@Transactional(rollbackFor = Exception.class)
public void batchOperation( List<AsnDetail> updateToAsnDetail,List<ReceiveRecord> createToReceiveRecord , List<Inventory> createToInventory, List<InventoryLog> createToInventoryLog) {
if (CollectionUtils.isNotEmpty(updateToAsnDetail)) {
batchUtil.updateBatchAsnDetail(updateToAsnDetail);
}
if (CollectionUtils.isNotEmpty(createToReceiveRecord)) {
batchUtil.saveBatchReceiveRecord(createToReceiveRecord);
}
if (CollectionUtils.isNotEmpty(createToInventory)) {
batchUtil.saveBatchInventory(createToInventory);
}
if (CollectionUtils.isNotEmpty(createToInventoryLog)) {
batchUtil.saveBatchInventoryLog(createToInventoryLog);
}
}
/**
*
*
* @param data
*/
private void refreshData(ReceiveData data) {
asnDetailService.refreshAsn(data.getAsn(), data.getAsnDetails());
}
/**
*
*
* @param stock
* @param dstPoint
*/
@Transactional(rollbackFor = Exception.class)
public void updateStockAndPoint(Stock stock, Point dstPoint) {
//更新容器状态和位置
stockService.bindStock(stock, dstPoint);
pointService.bindPoint(dstPoint);
}
}

View File

@ -1,31 +1,98 @@
package org.cpte.modules.receive.service.impl;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.cpte.modules.base.entity.Item;
import org.cpte.modules.base.entity.ItemKey;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.constant.enums.AsnStatusEnum;
import org.cpte.modules.receive.entity.Asn;
import org.cpte.modules.receive.entity.AsnDetail;
import org.cpte.modules.receive.entity.ReceiveRecord;
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.saiWms.request.InboundRequest;
import org.cpte.modules.shipping.mapper.PickMapper;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @Description:
* @author: cpte
* @Date: 2025-11-03
* @Date: 2025-11-03
* @Version: V1.0
*/
@Service
public class AsnDetailServiceImpl extends ServiceImpl<AsnDetailMapper, AsnDetail> implements IAsnDetailService {
@Override
public List<AsnDetail> selectByMainId(Long mainId) {
return this.baseMapper.selectByMainId(mainId);
}
@Autowired
private AsnMapper asnMapper;
@Override
public List<AsnDetail> selectByMainId(Long mainId) {
return this.baseMapper.selectByMainId(mainId);
}
public void refreshAsn(Asn asn, List<AsnDetail> asnDetails) {
if (asnDetails == null) {
asnDetails = new ArrayList<>();
}
//需求数量
BigDecimal orderQty = asnDetails.stream().map(AsnDetail::getOrderQty).reduce(BigDecimal.ZERO, BigDecimal::add);
//收货数量
BigDecimal receivedQty = asnDetails.stream().map(AsnDetail::getReceivedQty).reduce(BigDecimal.ZERO, BigDecimal::add);
//当前状态
Integer status = asn.getStatus();
if (orderQty.compareTo(BigDecimal.ZERO) <= 0) {
// 无需求量时设为创建状态
status = AsnStatusEnum.CREATED.getValue();
} else if (receivedQty.compareTo(BigDecimal.ZERO) <= 0) {
// 未收货时为创建状态
status = AsnStatusEnum.CREATED.getValue();
} else if (receivedQty.compareTo(orderQty) >= 0) {
// 已完全收货
status = AsnStatusEnum.RECEIVED.getValue();
} else {
// 部分收货
status = AsnStatusEnum.RECEIVING.getValue();
}
asn.setOrderQty(orderQty);
asn.setReceivedQty(receivedQty);
asn.setStatus(status);
asnMapper.updateById(asn);
}
@Override
public ReceiveRecord buildReceiveRecord(AsnDetail asnDetail, BigDecimal receivedQty, ItemKey itemKey, Long dstPointId) {
return ReceiveRecord.builder()
.id(IdWorker.getId())
.asnDetailId(asnDetail.getId())
.stockId(asnDetail.getStockId())
.fromPointId(asnDetail.getToPointId())
.toPointId(dstPointId)
.itemId(asnDetail.getItemId())
.itemKeyId(itemKey.getId())
.receivedQty(receivedQty)
.description(asnDetail.getDescription())
.tenantId(asnDetail.getTenantId())
.sysOrgCode(asnDetail.getSysOrgCode())
.createBy(asnDetail.getCreateBy())
.createTime(new Date())
.build();
}
}

View File

@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.cpte.modules.base.entity.Item;
import org.cpte.modules.base.entity.ItemKey;
@ -29,12 +30,15 @@ import org.cpte.modules.receive.entity.AsnDetail;
import org.cpte.modules.receive.entity.ReceiveRecord;
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.receive.service.ReceiveProcessor;
import org.cpte.modules.saiWms.request.InboundRequest;
import org.cpte.modules.saiWms.request.SaiWmsRequest;
import org.cpte.modules.saiWms.request.SMOMRequest;
import org.cpte.modules.serialNumber.AsnSerialNumberRule;
import org.cpte.modules.utils.BatchUtil;
import org.cpte.modules.utils.BigDecimalUtil;
import org.cpte.modules.utils.RedisDistributedLockUtil;
import org.cpte.modules.utils.SwmsLoginUtil;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.modules.openapi.mapper.OpenApiMapper;
@ -60,41 +64,54 @@ import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnService {
@Autowired
private StockMapper stockMapper;
@Autowired
private PointMapper pointMapper;
@Autowired
private AsnDetailMapper asnDetailMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private OpenApiMapper openApiMapper;
@Autowired
private SysDictMapper sysDictMapper;
@Autowired
private IStockService iStockService;
private IAsnDetailService asnDetailService;
@Autowired
private IPointService iPointService;
@Autowired
private IItemKeyService iItemKeyService;
@Autowired
private IInventoryService iInventoryService;
@Autowired
private IInventoryLogService iInventoryLogService;
private ReceiveProcessor receiveProcessor;
@Autowired
private SwmsLoginUtil swmsLoginUtil;
@Autowired
private BatchUtil batchUtil;
@Autowired
private AsnSerialNumberRule asnSerialNumberRule;
@Autowired
private RedisDistributedLockUtil redissonLock;
@Override
@Transactional(rollbackFor = Exception.class)
public void saveMain(Asn asn, List<AsnDetail> asnDetailList) {
String lockKey = "asn:" + asn.getNo();
String lockValue = null;
try {
lockValue = redissonLock.tryLock(lockKey, 10);
if (StringUtils.isEmpty(lockValue)) {
throw new RuntimeException("入库处理中,请稍后重试");
}
processorSaveMain(asn, asnDetailList);
} finally {
if (StringUtils.isNotEmpty(lockValue)) {
redissonLock.unlock(lockKey, lockValue);
}
}
}
/**
*
*
* @param asn
* @param asnDetailList
*/
@Transactional(rollbackFor = Exception.class)
public void processorSaveMain(Asn asn, List<AsnDetail> asnDetailList) {
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
asn.setTenantId(Long.parseLong(sysUser.getRelTenantIds()));
asn.setSysOrgCode(sysUser.getOrgCode());
String orderNo = asnSerialNumberRule.generateSerialNumber(GeneralConstant.ASN_ORDER_NO);
asn.setOrderNo(orderNo);
this.baseMapper.insert(asn);
if (asnDetailList == null || asnDetailList.isEmpty()) {
@ -112,43 +129,9 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
asnDetailMapper.insert(entity);
}
//刷新入库单
refreshAsn(asn, asnDetailList);
asnDetailService.refreshAsn(asn, asnDetailList);
}
public synchronized void refreshAsn(Asn asn, List<AsnDetail> asnDetails) {
if (asnDetails == null) {
asnDetails = new ArrayList<>();
}
//需求数量
BigDecimal orderQty = asnDetails.stream().map(AsnDetail::getOrderQty).reduce(BigDecimal.ZERO, BigDecimal::add);
//收货数量
BigDecimal receivedQty = asnDetails.stream().map(AsnDetail::getReceivedQty).reduce(BigDecimal.ZERO, BigDecimal::add);
//当前状态
Integer status = asn.getStatus();
if (orderQty.compareTo(BigDecimal.ZERO) <= 0) {
// 无需求量时设为创建状态
status = AsnStatusEnum.CREATED.getValue();
} else if (receivedQty.compareTo(BigDecimal.ZERO) <= 0) {
// 未收货时为创建状态
status = AsnStatusEnum.CREATED.getValue();
} else if (receivedQty.compareTo(orderQty) >= 0) {
// 已完全收货
status = AsnStatusEnum.RECEIVED.getValue();
} else {
// 部分收货
status = AsnStatusEnum.RECEIVING.getValue();
}
asn.setOrderQty(orderQty);
asn.setReceivedQty(receivedQty);
asn.setStatus(status);
this.baseMapper.updateById(asn);
}
@Override
@Transactional(rollbackFor = Exception.class)
@ -176,7 +159,7 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
}
}
// 刷新入库单状态
refreshAsn(asn, asnDetailList);
asnDetailService.refreshAsn(asn, asnDetailList);
}
@ -211,9 +194,7 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
@Override
public Asn buildAsn(InboundRequest inboundRequest) {
String orderNo = asnSerialNumberRule.generateSerialNumber(GeneralConstant.ASN_ORDER_NO);
return Asn.builder()
.orderNo(orderNo)
.thirdOrderNo(inboundRequest.getOrderNo())
.no(inboundRequest.getNo())
.whCode(inboundRequest.getWhCode())
@ -236,7 +217,7 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
.receivedQty(BigDecimal.ZERO)
.stockId(stock.getId())
.fromPointId(srcPoint == null ? null : srcPoint.getId())
.toPointId(dstPoint.getId())
.toPointId(dstPoint == null ? null : dstPoint.getId())
.status(AsnStatusEnum.CREATED.getValue())
.project(detail.getProject())
.taskNo(detail.getTaskNo())
@ -249,80 +230,24 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
}
@Override
@Transactional(rollbackFor = Exception.class)
public void receiveGoods(Long asnId, String pointCode) {
//入库任务
Asn asn = this.baseMapper.selectById(asnId);
if (asn == null) {
throw new RuntimeException("未匹配到入库任务【" + asnId + "】");
}
List<AsnDetail> asnDetails = asnDetailMapper.selectByMainId(asnId);
//验证容器是否入库
Stock stock = stockMapper.selectById(asnDetails.get(0).getStockId());
if (inventoryMapper.queryByStockId(stock.getId()) != null) {
throw new RuntimeException("【" + stock.getStockCode() + "】托盘已入库");
}
//入库实际库位
Point dstPoint = pointMapper.queryByPointCode(pointCode);
//批量操作
List<AsnDetail> updateToAsnDetail = new ArrayList<>();
List<ReceiveRecord> createToReceiveRecord = new ArrayList<>();
List<Inventory> createToInventory = new ArrayList<>();
List<InventoryLog> createToInventoryLog = new ArrayList<>();
//收货逻辑
for (AsnDetail asnDetail : asnDetails) {
BigDecimal receivedQty = BigDecimalUtil.add(asnDetail.getReceivedQty(), asnDetail.getOrderQty(), 0);
asnDetail.setReceivedQty(receivedQty);
//更新明细状态
if (receivedQty.compareTo(asnDetail.getOrderQty()) >= 0) {
asnDetail.setStatus(AsnStatusEnum.RECEIVED.getValue());
} else {
asnDetail.setStatus(AsnStatusEnum.RECEIVING.getValue());
public void receiveAsn(Long asnId, String pointCode) {
String lockKey = "asn:" + asnId;
String lockValue = null;
try {
lockValue = redissonLock.tryLock(lockKey, 10);
if (StringUtils.isEmpty(lockValue)) {
throw new RuntimeException("收货处理中,请稍后重试");
}
receiveProcessor.receiveAsn(asnId, pointCode);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (StringUtils.isNotEmpty(lockValue)) {
redissonLock.unlock(lockKey, lockValue);
}
updateToAsnDetail.add(asnDetail);
//获取itemKey
ItemKey itemKey = iItemKeyService.createItemKey(asnDetail.getItemId(), asn.getWhCode(), asnDetail.getProject(), asnDetail.getTaskNo(), asnDetail.getPropC1(), asnDetail.getPropC3());
//生成入库记录
ReceiveRecord receiveRecord = buildReceiveRecord(asnDetail, receivedQty, itemKey, dstPoint.getId());
createToReceiveRecord.add(receiveRecord);
// 生成库存
Inventory inventory = iInventoryService.buildInventory(stock.getId(), receivedQty, asn, receiveRecord);
createToInventory.add(inventory);
//添加库存日志
InventoryLog inventoryLog = iInventoryLogService.buildInboundInventoryLog(inventory, asnDetail.getToPointId(), receivedQty, asn.getOrderNo(), asnDetail.getId(), asnDetail.getDescription());
createToInventoryLog.add(inventoryLog);
}
if (CollectionUtils.isNotEmpty(updateToAsnDetail)) {
batchUtil.updateBatchAsnDetail(updateToAsnDetail);
}
if (CollectionUtils.isNotEmpty(createToReceiveRecord)) {
batchUtil.saveBatchReceiveRecord(createToReceiveRecord);
}
if (CollectionUtils.isNotEmpty(createToInventory)) {
batchUtil.saveBatchInventory(createToInventory);
}
if (CollectionUtils.isNotEmpty(createToInventoryLog)) {
batchUtil.saveBatchInventoryLog(createToInventoryLog);
}
//更新入库单
refreshAsn(asn, asnDetails);
//更新容器状态和位置
iStockService.bindStock(stock, dstPoint);
iPointService.bindPoint(dstPoint);
//回传
receiveCallback(asn, stock);
}
@ -331,7 +256,7 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
*/
private String receiveCallbackJson(Asn asn, Stock stock, String ticket) {
SaiWmsRequest.Task task = new SaiWmsRequest.Task();
SMOMRequest.Task task = new SMOMRequest.Task();
task.setNo(asn.getNo());
task.setOrderNo(asn.getThirdOrderNo());
task.setState(5);
@ -343,17 +268,17 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
task.setIsDelete(false);
task.setLastUpdateDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
SaiWmsRequest.ParameterValue1 parameterValue1 = new SaiWmsRequest.ParameterValue1();
SMOMRequest.ParameterValue1 parameterValue1 = new SMOMRequest.ParameterValue1();
parameterValue1.setValue(List.of(task));
SaiWmsRequest.ParameterValue2 parameterValue2 = new SaiWmsRequest.ParameterValue2();
SMOMRequest.ParameterValue2 parameterValue2 = new SMOMRequest.ParameterValue2();
parameterValue2.setValue(1);
SaiWmsRequest.Context context = new SaiWmsRequest.Context();
SMOMRequest.Context context = new SMOMRequest.Context();
context.setInvOrgId(1);
context.setTicket(ticket);
SaiWmsRequest saiWmsRequest = new SaiWmsRequest();
SMOMRequest saiWmsRequest = new SMOMRequest();
saiWmsRequest.setApiType("SmomWebApiController");
saiWmsRequest.setParameters(List.of(parameterValue1, parameterValue2));
saiWmsRequest.setMethod("AutomatedWarehouseTasks");
@ -434,26 +359,4 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
asnDetailMapper.updateById(updateToAsnDetail);
}
}
/**
*
*/
public ReceiveRecord buildReceiveRecord(AsnDetail asnDetail, BigDecimal receivedQty, ItemKey itemKey, Long dstPointId) {
return ReceiveRecord.builder()
.id(IdWorker.getId())
.asnDetailId(asnDetail.getId())
.stockId(asnDetail.getStockId())
.fromPointId(asnDetail.getToPointId())
.toPointId(dstPointId)
.itemId(asnDetail.getItemId())
.itemKeyId(itemKey.getId())
.receivedQty(receivedQty)
.description(asnDetail.getDescription())
.tenantId(asnDetail.getTenantId())
.sysOrgCode(asnDetail.getSysOrgCode())
.createBy(asnDetail.getCreateBy())
.createTime(new Date())
.build();
}
}

View File

@ -0,0 +1,17 @@
package org.cpte.modules.receive.vo;
import lombok.Data;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.receive.entity.Asn;
import org.cpte.modules.receive.entity.AsnDetail;
import java.util.List;
@Data
public class ReceiveData {
private Asn asn ;
private List<AsnDetail> asnDetails;
private Stock stock;
private Point dstPoint;
}

View File

@ -7,10 +7,9 @@ import lombok.extern.slf4j.Slf4j;
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.ISaiWmsService;
import org.cpte.modules.saiWms.service.ISMOMService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.config.shiro.IgnoreAuth;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@ -24,7 +23,7 @@ import org.springframework.web.bind.annotation.RestController;
public class SaiWmsController {
@Autowired
private ISaiWmsService iSaiWmsService;
private ISMOMService iSaiWmsService;
/**
*

View File

@ -68,7 +68,7 @@ public class OutboundRequest {
// 数量
@NotNull(message = "数量不能为空")
@JsonProperty("Qty")
private BigDecimal qty;
private Double qty;
// 项目号
@JsonProperty("Project")

View File

@ -6,7 +6,7 @@ import lombok.Data;
import java.util.List;
@Data
public class SaiWmsRequest {
public class SMOMRequest {
@JsonProperty("ApiType")
private String apiType;

View File

@ -4,7 +4,7 @@ import org.cpte.modules.saiWms.request.InboundRequest;
import org.cpte.modules.saiWms.request.OutboundRequest;
import org.cpte.modules.saiWms.request.SyncStockRequest;
public interface ISaiWmsService {
public interface ISMOMService {
/**
*

View File

@ -0,0 +1,247 @@
package org.cpte.modules.saiWms.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.cpte.modules.agvTask.service.IAgvTaskService;
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.GeneralConstant;
import org.cpte.modules.constant.enums.AgvVendorEnum;
import org.cpte.modules.constant.enums.AreaTypeEnum;
import org.cpte.modules.constant.enums.AsnOrderTypeEnum;
import org.cpte.modules.constant.enums.BusinessTypeEnum;
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.AsnMapper;
import org.cpte.modules.receive.service.IAsnService;
import org.cpte.modules.saiWms.request.InboundRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
*
*/
@Service
@Slf4j
public class InBoundTaskProcessor {
@Autowired
private AsnMapper asnMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private IItemService itemService;
@Autowired
private IStockService stockService;
@Autowired
private IPointService pointService;
@Autowired
private IAsnService asnService;
@Autowired
private IAgvTaskService agvTaskService;
/**
*
*
* @param inboundRequest
*/
public void inBoundTask(InboundRequest inboundRequest) {
// 1.参数校验
Stock stock = validateParams(inboundRequest);
// 2.验证任务号
validateAsn(inboundRequest.getNo());
//3.验证物料
Map<String, Item> itemMap = validateItem(inboundRequest.getDetails());
//4.验证起点
Point srcPoint = validateSrcPoint(inboundRequest.getType(), inboundRequest.getLocationFrom());
//5.入库处理
processInboundTask(inboundRequest, itemMap, srcPoint, stock);
}
/**
*
*
* @param inboundRequest
* @param itemMap
* @param srcPoint
* @param stock
*/
@Transactional(rollbackFor = Exception.class)
private void processInboundTask(InboundRequest inboundRequest, Map<String, Item> itemMap, Point srcPoint, Stock stock) {
//1.获取终点
Point dstPoint = getDstPoint(inboundRequest.getType());
//2.构建入库单和入库明细
Asn createAsn = asnService.buildAsn(inboundRequest);
List<AsnDetail> asnDetails = asnService.buildAsnDetail(inboundRequest.getDetails(), itemMap, stock, srcPoint, dstPoint);
asnService.saveMain(createAsn, asnDetails);
//3.绑定容器和起点
stockService.bindStock(stock, srcPoint);
//4.成品入库生成AGV任务
if (AsnOrderTypeEnum.PRODUCT.getValue().equals(inboundRequest.getType())) {
agvTaskService.createAgvTask(createAsn.getId(), stock.getStockCode(), srcPoint.getPointCode(), dstPoint.getPointCode(), null, BusinessTypeEnum.INBOUND.getValue(), 0, AgvVendorEnum.HIK.getValue());
}
}
/**
*
*
* @param inboundRequest
* @return Stock
*/
private Stock validateParams(InboundRequest inboundRequest) {
if (StringUtils.isBlank(inboundRequest.getNo())) {
throw new RuntimeException("任务号(No)必填");
}
if (StringUtils.isBlank(inboundRequest.getOrderNo())) {
throw new RuntimeException("单号(OrderNo)必填");
}
if (StringUtils.isBlank(inboundRequest.getWhCode())) {
throw new RuntimeException("仓库(WhCode)必填");
}
if (inboundRequest.getType() == null) {
throw new RuntimeException("任务类型(Type)必填");
}
if (AsnOrderTypeEnum.PRODUCT.getValue().equals(inboundRequest.getType())) {
if (StringUtils.isBlank(inboundRequest.getLocationFrom())) {
throw new RuntimeException("起点(LocationFrom)必填");
}
}
if (CollectionUtils.isEmpty(inboundRequest.getDetails())) {
throw new RuntimeException("入库信息不能为空");
}
// 校验每个detail项
for (InboundRequest.InboundDetail detail : inboundRequest.getDetails()) {
if (StringUtils.isBlank(detail.getLineNo())) {
throw new RuntimeException("行号(LineNo)必填");
}
if (StringUtils.isBlank(detail.getItem())) {
throw new RuntimeException("物料(Item)必填");
}
if (StringUtils.isBlank(detail.getUnit())) {
throw new RuntimeException("单位(Unit)必填");
}
if (detail.getQty() <= 0) {
throw new RuntimeException("数量(Qty)必须大于0");
}
if (StringUtils.isBlank(detail.getLpn())) {
throw new RuntimeException("托盘号(Lpn)必填");
}
}
//验证明细中托盘只能是同一个,如果明细中托盘有不一样的则提示
Set<String> lpns = inboundRequest.getDetails().stream().map(InboundRequest.InboundDetail::getLpn).collect(Collectors.toSet());
if (lpns.size() > 1) {
throw new RuntimeException("明细中托盘只能是同一个");
}
//验证明细中物料只能是同一种,如果明细中物料有不一样的则提示
Set<String> itemCodes = inboundRequest.getDetails().stream().map(InboundRequest.InboundDetail::getItem).collect(Collectors.toSet());
if (itemCodes.size() > 1) {
throw new RuntimeException("明细中物料只能是同一种");
}
//获取明细托盘
String lpn = lpns.iterator().next();
Stock stock = stockService.validateStock(lpn);
if (inventoryMapper.exitsStockInventory(stock.getId()) != null) {
throw new RuntimeException("【" + lpn + "】托盘已入库");
}
return stock;
}
/**
*
*
* @param no
*/
private void validateAsn(String no) {
Asn asn = asnMapper.queryByNo(no);
if (asn != null) {
throw new RuntimeException("【" + no + "】任务号已接收,请勿重复下发");
}
}
/**
*
*
* @param detail
* @return Map<String, Item>
*/
private Map<String, Item> validateItem(List<InboundRequest.InboundDetail> detail) {
//获取明细中所有的物料
List<String> itemCodes = detail.stream().map(InboundRequest.InboundDetail::getItem).toList();
//获取存在的物料
Map<String, Item> exitItemMap = itemService.queryByItemCodesToMap(itemCodes);
//获取数据库不存在的物料集合且去重
List<String> notExistItemCodes = itemCodes.stream().filter(itemCode -> !exitItemMap.containsKey(itemCode)).distinct().toList();
if (CollectionUtils.isNotEmpty(notExistItemCodes)) {
throw new RuntimeException("【" + notExistItemCodes + "】物料不存在");
}
return exitItemMap;
}
/**
*
*
* @param orderType
* @param srcPointCode
* @return Point
*/
private Point validateSrcPoint(Integer orderType, String srcPointCode) {
Point srcPoint = null;
if (AsnOrderTypeEnum.PRODUCT.getValue().equals(orderType)) {
srcPoint = pointService.validatePoint(srcPointCode);
}
return srcPoint;
}
/**
*
*
* @param orderType
* @return Point
*/
private Point getDstPoint(Integer orderType) {
Point dstPoint = null;
if (AsnOrderTypeEnum.PRODUCT.getValue().equals(orderType)) {
//1.获取入库输送线工作台点位为终点,均衡分配点位-轮询方式
dstPoint = pointService.getWorkStationPoint(null, AreaTypeEnum.RK_DOCK.getValue(), GeneralConstant.RK_DOCK_TASK_INDEX);
}
return dstPoint;
}
}

View File

@ -0,0 +1,187 @@
package org.cpte.modules.saiWms.service;
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.service.IItemService;
import org.cpte.modules.base.service.IPointService;
import org.cpte.modules.constant.GeneralConstant;
import org.cpte.modules.constant.enums.AreaTypeEnum;
import org.cpte.modules.constant.enums.AsnOrderTypeEnum;
import org.cpte.modules.saiWms.request.OutboundRequest;
import org.cpte.modules.shipping.entity.Pick;
import org.cpte.modules.shipping.entity.PickDetail;
import org.cpte.modules.shipping.mapper.PickMapper;
import org.cpte.modules.shipping.service.IPickService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
/**
*
*/
@Service
@Slf4j
public class OutBoundTaskProcessor {
@Autowired
private PickMapper pickMapper;
@Autowired
private IItemService itemService;
@Autowired
private IPointService pointService;
@Autowired
private IPickService pickService;
/**
*
*
* @param outboundRequest
*/
public void outBoundTask(OutboundRequest outboundRequest) {
// 1.参数校验
validateParams(outboundRequest);
// 2.验证任务号
validatePick(outboundRequest.getNo());
//3.验证物料
Map<String, Item> itemMap = validateItem(outboundRequest.getDetails());
//5.出库处理
processOutBoundTask(outboundRequest, itemMap);
}
/**
*
*
* @param outboundRequest
* @param itemMap
*/
@Transactional(rollbackFor = Exception.class)
private void processOutBoundTask(OutboundRequest outboundRequest, Map<String, Item> itemMap) {
// 创建出库单和明细
Pick createPick = pickService.buildPick(outboundRequest);
List<PickDetail> pickDetails = pickService.buildPickDetail(outboundRequest.getDetails(), itemMap);
pickService.saveMain(createPick, pickDetails);
}
/**
*
*
* @param outboundRequest
*/
private void validateParams(OutboundRequest outboundRequest) {
if (StringUtils.isBlank(outboundRequest.getNo())) {
throw new RuntimeException("任务号(No)必填");
}
if (StringUtils.isBlank(outboundRequest.getOrderNo())) {
throw new RuntimeException("单号(OrderNo)必填");
}
if (StringUtils.isBlank(outboundRequest.getWhCode())) {
throw new RuntimeException("仓库(WhCode)必填");
}
if (outboundRequest.getType() == null) {
throw new RuntimeException("任务类型(Type)必填");
}
if (CollectionUtils.isEmpty(outboundRequest.getDetails())) {
throw new RuntimeException("出库信息不能为空");
}
// 校验每个detail项
for (OutboundRequest.OutboundDetail detail : outboundRequest.getDetails()) {
if (StringUtils.isBlank(detail.getLineNo())) {
throw new RuntimeException("行号(LineNo)必填");
}
if (StringUtils.isBlank(detail.getItem())) {
throw new RuntimeException("物料(Item)必填");
}
if (StringUtils.isBlank(detail.getUnit())) {
throw new RuntimeException("单位(Unit)必填");
}
if (detail.getQty() <= 0) {
throw new RuntimeException("数量(Qty)必须大于0");
}
}
}
/**
*
*
* @param no
*/
private void validatePick(String no) {
Pick pick = pickMapper.queryByNo(no);
if (pick != null) {
throw new RuntimeException("【" + no + "】任务号已接收,请勿重复下发");
}
}
/**
*
*
* @param detail
* @return Map<String, Item>
*/
private Map<String, Item> validateItem(List<OutboundRequest.OutboundDetail> detail) {
//获取明细中所有的物料
List<String> itemCodes = detail.stream().map(OutboundRequest.OutboundDetail::getItem).toList();
//获取数据库已存在物料
Map<String, Item> exitItemMap = itemService.queryByItemCodesToMap(itemCodes);
//获取不存在的物料
List<String> notExitItemCodes = itemCodes.stream().filter(itemCode -> !exitItemMap.containsKey(itemCode)).toList();
if (CollectionUtils.isNotEmpty(notExitItemCodes)) {
throw new RuntimeException("系统无" + notExitItemCodes + "物料,请维护");
}
return exitItemMap;
}
/**
*
*
* @param orderType
* @param srcPointCode
* @return Point
*/
private Point validateSrcPoint(Integer orderType, String srcPointCode) {
Point srcPoint = null;
if (AsnOrderTypeEnum.PRODUCT.getValue().equals(orderType)) {
srcPoint = pointService.validatePoint(srcPointCode);
}
return srcPoint;
}
/**
*
*
* @param orderType
* @return Point
*/
private Point getDstPoint(Integer orderType) {
Point dstPoint = null;
if (AsnOrderTypeEnum.PRODUCT.getValue().equals(orderType)) {
//1.获取入库输送线工作台点位为终点,均衡分配点位-轮询方式
dstPoint = pointService.getWorkStationPoint(null, AreaTypeEnum.RK_DOCK.getValue(), GeneralConstant.RK_DOCK_TASK_INDEX);
}
return dstPoint;
}
}

View File

@ -0,0 +1,145 @@
package org.cpte.modules.saiWms.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.base.service.IStockService;
import org.cpte.modules.saiWms.request.SyncStockRequest;
import org.cpte.modules.utils.BatchUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
/**
*
*/
@Service
@Slf4j
public class SyncStockProcessor {
@Autowired
private IStockService stockService;
@Autowired
private BatchUtil batchUtil;
/**
*
*
* @param syncStockRequest
*/
public void syncStock(SyncStockRequest syncStockRequest) {
// 1.参数校验
validateParams(syncStockRequest);
// 2.获取容器
Map<String, Stock> stockMap = getStocks(syncStockRequest.getStocks());
//3.创建数据结构
List<Stock> insertToStock = new ArrayList<>();
List<Stock> updateToStock = new ArrayList<>();
//4.容器处理
processStock(syncStockRequest.getStocks(), stockMap, insertToStock, updateToStock);
//5.批量操作
batchOperation(insertToStock, updateToStock);
}
/**
*
*
* @param syncStockRequest
*/
private void validateParams(SyncStockRequest syncStockRequest) {
if (syncStockRequest.getStocks() == null || syncStockRequest.getStocks().isEmpty()) {
throw new RuntimeException("托盘信息不能为空");
}
// 校验每个stock项
for (SyncStockRequest.StockDTO stock : syncStockRequest.getStocks()) {
if (StringUtils.isBlank(stock.getCode())) {
throw new RuntimeException("容器编码(Code)不能为空");
}
if (stock.getIzActive() == null) {
throw new RuntimeException("启用状态(IzActive)不能为空");
}
if (stock.getDelFlag() == null) {
throw new RuntimeException("删除标志(DelFlag)不能为空");
}
// 校验 IzActive 有效性 (假设只能是0或1)
if (stock.getIzActive() != 0 && stock.getIzActive() != 1) {
throw new RuntimeException("启用状态(IzActive)必须为0或1");
}
// 校验 DelFlag 有效性 (假设只能是0或1)
if (stock.getDelFlag() != 0 && stock.getDelFlag() != 1) {
throw new RuntimeException("删除标志(DelFlag)必须为0或1");
}
}
}
/**
*
*
* @param stocks
* @return Map<String, Stock>
*/
private Map<String, Stock> getStocks(List<SyncStockRequest.StockDTO> stocks) {
List<String> stockCodes = stocks.stream().map(SyncStockRequest.StockDTO::getCode).distinct().toList();
return stockService.queryByStockCodesToMap(stockCodes);
}
/**
*
*
* @param stocks
* @param stockMap Map
* @param insertToStock
* @param updateToStock
*/
private void processStock(List<SyncStockRequest.StockDTO> stocks, Map<String, Stock> stockMap, List<Stock> insertToStock, List<Stock> updateToStock) {
Set<String> processedStockCodes = new HashSet<>();
for (SyncStockRequest.StockDTO stockDTO : stocks) {
String stockCode = stockDTO.getCode();
// 去重处理
if (processedStockCodes.contains(stockCode)) {
continue;
}
processedStockCodes.add(stockCode);
Stock exitStock = stockMap.get(stockDTO.getCode());
if (exitStock == null) {
Stock createStock = stockService.buildStocK(stockDTO);
insertToStock.add(createStock);
} else {
exitStock.setIzActive(stockDTO.getIzActive());
exitStock.setDelFlag(stockDTO.getDelFlag());
updateToStock.add(exitStock);
}
}
}
/**
*
*
* @param insertToStock
* @param updateToStock
*/
@Transactional(rollbackFor = Exception.class)
public void batchOperation(List<Stock> insertToStock, List<Stock> updateToStock) {
if (CollectionUtils.isNotEmpty(insertToStock)) {
batchUtil.saveBatchStock(insertToStock);
}
if (CollectionUtils.isNotEmpty(updateToStock)) {
batchUtil.batchUpdateStocks(updateToStock);
}
}
}

View File

@ -0,0 +1,42 @@
package org.cpte.modules.saiWms.service.impl;
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.InBoundTaskProcessor;
import org.cpte.modules.saiWms.service.OutBoundTaskProcessor;
import org.cpte.modules.saiWms.service.SyncStockProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ISMOMServiceImpl implements ISMOMService {
@Autowired
private InBoundTaskProcessor inBoundTaskProcessor;
@Autowired
private OutBoundTaskProcessor outBoundTaskProcessor;
@Autowired
private SyncStockProcessor syncStockProcessor;
@Override
public void inBoundTask(InboundRequest inboundRequest) {
inBoundTaskProcessor.inBoundTask(inboundRequest);
}
@Override
public void outBoundTask(OutboundRequest outboundRequest) {
outBoundTaskProcessor.outBoundTask(outboundRequest);
}
@Override
public void syncStock(SyncStockRequest syncStockRequest) {
syncStockProcessor.syncStock(syncStockRequest);
}
}

View File

@ -1,300 +0,0 @@
package org.cpte.modules.saiWms.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.cpte.modules.agvTask.service.IAgvTaskService;
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.GeneralConstant;
import org.cpte.modules.constant.enums.*;
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.AsnMapper;
import org.cpte.modules.receive.service.IAsnService;
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.ISaiWmsService;
import org.cpte.modules.shipping.entity.Pick;
import org.cpte.modules.shipping.entity.PickDetail;
import org.cpte.modules.shipping.mapper.PickMapper;
import org.cpte.modules.shipping.service.IPickService;
import org.cpte.modules.utils.BatchUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class ISaiWmsServiceImpl implements ISaiWmsService {
@Autowired
private AsnMapper asnMapper;
@Autowired
private PickMapper pickMapper;
private InventoryMapper inventoryMapper;
@Autowired
private IItemService itemService;
@Autowired
private IStockService iStockService;
@Autowired
private IPointService iPointService;
@Autowired
private IAsnService asnService;
@Autowired
private IPickService pickService;
@Autowired
private IAgvTaskService iAgvTaskService;
@Autowired
private BatchUtil batchUtil;
private Stock validateParams(InboundRequest inboundRequest) {
if (StringUtils.isBlank(inboundRequest.getNo())) {
throw new RuntimeException("任务号(No)必填");
}
if (StringUtils.isBlank(inboundRequest.getOrderNo())) {
throw new RuntimeException("单号(OrderNo)必填");
}
if (StringUtils.isBlank(inboundRequest.getWhCode())) {
throw new RuntimeException("仓库(WhCode)必填");
}
if (inboundRequest.getType() == null) {
throw new RuntimeException("任务类型(Type)必填");
}
if (AsnOrderTypeEnum.PRODUCT.getValue().equals(inboundRequest.getType())) {
if (StringUtils.isBlank(inboundRequest.getLocationFrom())) {
throw new RuntimeException("起点(LocationFrom)必填");
}
}
if (CollectionUtils.isEmpty(inboundRequest.getDetails())) {
throw new RuntimeException("入库信息不能为空");
}
// 校验每个detail项
for (InboundRequest.InboundDetail detail : inboundRequest.getDetails()) {
if (StringUtils.isBlank(detail.getLineNo())) {
throw new RuntimeException("行号(LineNo)必填");
}
if (StringUtils.isBlank(detail.getItem())) {
throw new RuntimeException("物料(Item)必填");
}
if (StringUtils.isBlank(detail.getUnit())) {
throw new RuntimeException("单位(Unit)必填");
}
if (detail.getQty() <= 0) {
throw new RuntimeException("数量(Qty)必须大于0");
}
if (StringUtils.isBlank(detail.getLpn())) {
throw new RuntimeException("托盘号(Lpn)必填");
}
}
//验证明细中托盘只能是同一个,如果明细中托盘有不一样的则提示
Set<String> lpns = inboundRequest.getDetails().stream().map(InboundRequest.InboundDetail::getLpn).collect(Collectors.toSet());
if (lpns.size() > 1) {
throw new RuntimeException("明细中托盘只能是同一个");
}
//验证明细中物料只能是同一种,如果明细中物料有不一样的则提示
Set<String> itemCodes = inboundRequest.getDetails().stream().map(InboundRequest.InboundDetail::getItem).collect(Collectors.toSet());
if (itemCodes.size() > 1) {
throw new RuntimeException("明细中物料只能是同一种");
}
//获取明细托盘
String lpn = lpns.iterator().next();
Stock stock = iStockService.validateStock(lpn);
if (inventoryMapper.queryByStockId(stock.getId()) != null) {
throw new RuntimeException("【" + lpn + "】托盘已入库");
}
return stock;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void inBoundTask(InboundRequest inboundRequest) {
//验证参数
Stock stock = validateParams(inboundRequest);
//验证任务号
String no = inboundRequest.getNo();
Asn asn = asnMapper.queryByNo(no);
if (asn != null) {
throw new RuntimeException("【" + no + "】任务号已接收,请勿重复下发");
}
// 获取明细
List<InboundRequest.InboundDetail> detail = inboundRequest.getDetails();
//获取明细中所有的物料
List<String> itemCodes = detail.stream().map(InboundRequest.InboundDetail::getItem).toList();
//获取存在的物料
Map<String, Item> exitItemMap = itemService.queryByItemCodesToMap(itemCodes);
//获取数据库不存在的物料集合且去重
List<String> notExistItemCodes = itemCodes.stream().filter(itemCode -> !exitItemMap.containsKey(itemCode)).distinct().toList();
if (CollectionUtils.isNotEmpty(notExistItemCodes)) {
throw new RuntimeException("【" + notExistItemCodes + "】物料不存在");
}
//起点
Point srcPoint = null;
if (AsnOrderTypeEnum.PRODUCT.getValue().equals(inboundRequest.getType())) {
String srcPointCode = inboundRequest.getLocationFrom();
srcPoint = iPointService.validatePoint(srcPointCode);
}
//获取输送线工作台点位,均衡分配点位任务-轮询方式
Point dstPoint = iPointService.getWorkStationPoint(null, AreaTypeEnum.RK_DOCK.getValue(), GeneralConstant.RK_DOCK_TASK_INDEX);
// 创建入库单和明细
Asn createAsn = asnService.buildAsn(inboundRequest);
List<AsnDetail> asnDetails = asnService.buildAsnDetail(inboundRequest.getDetails(), exitItemMap, stock, srcPoint, dstPoint);
// 保存入库单和入库明细
asnService.saveMain(createAsn, asnDetails);
//绑定容器和起点
iStockService.bindStock(stock, srcPoint);
//成品入库需要生成AGV
if (AsnOrderTypeEnum.PRODUCT.getValue().equals(createAsn.getOrderType())) {
//创建AGV任务
String srcPointCode = srcPoint == null ? null : srcPoint.getPointCode();
iAgvTaskService.createAgvTask(createAsn.getId(), stock.getStockCode(), srcPointCode, dstPoint.getPointCode(), null, BusinessTypeEnum.INBOUND.getValue(), 0, AgvVendorEnum.HIK.getValue());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void outBoundTask(OutboundRequest outboundRequest) {
//验证任务号
String no = outboundRequest.getNo();
Pick pick = pickMapper.queryByNo(no);
if (pick != null) {
throw new RuntimeException("【" + no + "】任务号已接收,请勿重复下发");
}
// 获取集合中所有的物料
List<String> itemCodes = outboundRequest.getDetails().stream().map(OutboundRequest.OutboundDetail::getItem).distinct().toList();
//获取数据库已存在物料
Map<String, Item> exitItemMap = itemService.queryByItemCodesToMap(itemCodes);
//获取不存在的物料
List<String> notExitItemCodes = itemCodes.stream().filter(itemCode -> !exitItemMap.containsKey(itemCode)).toList();
if (CollectionUtils.isNotEmpty(notExitItemCodes)) {
throw new RuntimeException("系统无" + notExitItemCodes + "物料,请维护");
}
// 创建出库单和明细
Pick createPick = pickService.buildPick(outboundRequest);
List<PickDetail> pickDetails = pickService.buildPickDetail(outboundRequest.getDetails(), exitItemMap);
// 保存出库单和出库明细
pickService.saveMain(createPick, pickDetails);
}
//参数校验
private void validateParams(SyncStockRequest syncStockRequest) {
if (syncStockRequest.getStocks() == null || syncStockRequest.getStocks().isEmpty()) {
throw new RuntimeException("托盘信息不能为空");
}
// 校验每个stock项
for (SyncStockRequest.StockDTO stock : syncStockRequest.getStocks()) {
if (StringUtils.isBlank(stock.getCode())) {
throw new RuntimeException("容器编码(Code)不能为空");
}
if (stock.getIzActive() == null) {
throw new RuntimeException("启用状态(IzActive)不能为空");
}
if (stock.getDelFlag() == null) {
throw new RuntimeException("删除标志(DelFlag)不能为空");
}
// 校验 IzActive 有效性 (假设只能是0或1)
if (stock.getIzActive() != 0 && stock.getIzActive() != 1) {
throw new RuntimeException("启用状态(IzActive)必须为0或1");
}
// 校验 DelFlag 有效性 (假设只能是0或1)
if (stock.getDelFlag() != 0 && stock.getDelFlag() != 1) {
throw new RuntimeException("删除标志(DelFlag)必须为0或1");
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void syncStock(SyncStockRequest syncStockRequest) {
// 参数校验
validateParams(syncStockRequest);
//获取所有容器
List<String> stocks = syncStockRequest.getStocks().stream().map(SyncStockRequest.StockDTO::getCode).distinct().toList();
Map<String, Stock> stocksList = iStockService.queryByStockCodesToMap(stocks);
List<Stock> insertToStock = new ArrayList<>();
List<Stock> updateToStock = new ArrayList<>();
Set<String> processedStockCodes = new HashSet<>();
for (SyncStockRequest.StockDTO stockDTO : syncStockRequest.getStocks()) {
String stockCode = stockDTO.getCode();
// 去重处理
if (processedStockCodes.contains(stockCode)) {
continue;
}
processedStockCodes.add(stockCode);
Stock exitStock = stocksList.get(stockDTO.getCode());
if (exitStock == null) {
Stock createStock = iStockService.buildStocK(stockDTO);
insertToStock.add(createStock);
} else {
exitStock.setIzActive(stockDTO.getIzActive());
exitStock.setDelFlag(stockDTO.getDelFlag());
updateToStock.add(exitStock);
}
}
if (CollectionUtils.isNotEmpty(insertToStock)) {
batchUtil.saveBatchStock(insertToStock);
}
if (CollectionUtils.isNotEmpty(updateToStock)) {
batchUtil.batchUpdateStocks(updateToStock);
}
}
}

View File

@ -10,6 +10,8 @@ import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.cpte.modules.constant.GeneralConstant;
import org.cpte.modules.serialNumber.PickSerialNumberRule;
import org.cpte.modules.shipping.entity.Task;
import org.cpte.modules.shipping.service.ITaskService;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
@ -57,6 +59,8 @@ public class PickController {
@Autowired
private IPickDetailService pickDetailService;
@Autowired
private ITaskService taskService;
@Autowired
private PickSerialNumberRule pickSerialNumberRule;
/**
@ -183,6 +187,13 @@ public class PickController {
return Result.OK(pickDetailList);
}
@Operation(summary = "Task主表ID查询")
@GetMapping(value = "/queryTaskByMainId")
public Result<List<Task>> queryTaskByMainId(@RequestParam(name = "id", required = true) Long id) {
List<Task> pickDetailList = taskService.queryTaskByMainId(id);
return Result.OK(pickDetailList);
}
/**
* excel
*

View File

@ -97,6 +97,11 @@ public class Task implements Serializable {
@Schema(description = "出库明细ID")
@JsonSerialize(using = ToStringSerializer.class)
private java.lang.Long pickDetailId;
@Schema(description = "商品属性ID")
@JsonSerialize(using = ToStringSerializer.class)
private java.lang.Long itemKeyId;
/**
* ID
*/

View File

@ -23,7 +23,7 @@ public interface PickMapper extends BaseMapper<Pick> {
* @param no
* @return Pick
*/
@Select("select * from data_pick where no = #{no} for update ")
@Select("select * from data_pick where no = #{no} ")
Pick queryByNo(@Param("no") String no);
/**

View File

@ -21,11 +21,14 @@ public interface TaskMapper extends BaseMapper<Task> {
*
* @return List<Pick>
*/
@Select("SELECT * FROM data_task WHERE agv_task_id is null order by create_time for update ")
@Select("SELECT * FROM data_task WHERE agv_task_id is null order by create_time ")
List<Task> queryUnallocatedTask();
@Select("SELECT * FROM data_task WHERE agv_task_id = #{agvTaskId} ")
List<Task> queryByAgvTask(@Param("agvTaskId") Long agvTaskId);
List<Task> queryByPickIds(@Param("pickIds") List<Long> pickIds);
@Select("SELECT * FROM data_task WHERE pick_id = #{pickId} ")
List<Task> queryTaskByMainId(@Param("pickId")Long pickId);
}

View File

@ -0,0 +1,673 @@
package org.cpte.modules.shipping.service;
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.ItemKey;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.base.mapper.ItemKeyMapper;
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.inventory.entity.Inventory;
import org.cpte.modules.inventory.mapper.InventoryMapper;
import org.cpte.modules.inventoryLog.service.IInventoryLogService;
import org.cpte.modules.shipping.entity.Pick;
import org.cpte.modules.shipping.entity.PickDetail;
import org.cpte.modules.shipping.entity.Task;
import org.cpte.modules.shipping.mapper.PickDetailMapper;
import org.cpte.modules.shipping.vo.AllocationData;
import org.cpte.modules.shipping.vo.InventoryScore;
import org.cpte.modules.shipping.vo.ItemGroupKey;
import org.cpte.modules.utils.BatchUtil;
import org.cpte.modules.utils.BigDecimalUtil;
import org.cpte.modules.utils.RedisDistributedLockUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
/**
*
*/
@Service
@Slf4j
public class AllocateProcessor {
@Autowired
private PickDetailMapper pickDetailMapper;
@Autowired
private ItemKeyMapper itemKeyMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private IItemService itemService;
@Autowired
private IStockService stockService;
@Autowired
private IPointService pointService;
@Autowired
private IPickDetailService pickDetailService;
@Autowired
private ITaskService taskService;
@Autowired
private IInventoryLogService inventoryLogService;
@Autowired
private BatchUtil batchUtil;
@Autowired
private RedisDistributedLockUtil redissonLock;
/**
*
*
* @param pickIds ID
* @return
*/
public List<String> allocatePick(List<Long> pickIds) {
// 错误信息去重LinkedHashSet保证顺序和唯一
Set<String> errorMsgSet = new LinkedHashSet<>();
// 1.数据准备
AllocationData data = prepareAllocationData(pickIds);
//2.验证库存
if (!validateInventory(data, errorMsgSet)) {
return new ArrayList<>(errorMsgSet);
}
//3.创建数据结构
Map<Long, Inventory> inventoryUpdateMap = new HashMap<>();
Map<Long, PickDetail> pickDetailUpdateMap = new HashMap<>();
List<Task> createToTask = new ArrayList<>();
//4.分配
allocate(data, inventoryUpdateMap, pickDetailUpdateMap, createToTask, errorMsgSet);
//5.批量操作
batchOperation(inventoryUpdateMap, pickDetailUpdateMap, createToTask);
//6.刷新出库单
refreshData(data);
return new ArrayList<>(errorMsgSet);
}
/**
*
*
* @param pickIds ID
* @return AllocationData
*/
private AllocationData prepareAllocationData(List<Long> pickIds) {
AllocationData data = new AllocationData();
//查询出库单
Map<Long, Pick> pickMap = pickDetailService.queryByPickIdsToMap(pickIds);
data.setPickMap(pickMap);
//查询出库单明细
List<PickDetail> pickDetails = pickDetailMapper.queryByPickIds(pickIds);
data.setPickDetails(pickDetails);
//查询物料
List<Long> itemIds = pickDetails.stream().map(PickDetail::getItemId).distinct().toList();
Map<Long, Item> itemMap = itemService.queryByItemIdsToMap(itemIds);
data.setItemMap(itemMap);
//筛选查询库存的条件(非空去重)外部仓库、项目号、任务号、批次、外部库存状态
List<String> whCodeList = pickMap.values().stream().map(Pick::getWhCode).filter(StringUtils::isNotBlank).distinct().toList();
List<String> projectList = pickDetails.stream().map(PickDetail::getProject).filter(StringUtils::isNotBlank).distinct().toList();
List<String> taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).filter(StringUtils::isNotBlank).distinct().toList();
List<String> propC1List = pickDetails.stream().map(PickDetail::getPropC1).filter(StringUtils::isNotBlank).distinct().toList();
List<String> propC3List = pickDetails.stream().map(PickDetail::getPropC3).filter(StringUtils::isNotBlank).distinct().toList();
List<ItemKey> itemKeys = itemKeyMapper.queryItemKeys(itemIds, whCodeList, projectList, taskNoList, propC1List, propC3List);
//根据物料属性分组
Map<ItemGroupKey, ItemKey> itemGroupKey = itemKeys.stream()
.collect(Collectors.toMap(
itemKey -> ItemGroupKey.of(
itemKey.getItemId(),
itemKey.getWhCode(),
itemKey.getProject(),
itemKey.getTaskNo(),
itemKey.getPropC1(),
itemKey.getPropC3()
),
itemKey -> itemKey
));
data.setItemGroupMap(itemGroupKey);
//查询库存
List<Inventory> inventories = null;
List<Long> itemKeyIds = itemKeys.stream().map(ItemKey::getId).distinct().toList();
if (CollectionUtils.isNotEmpty(itemKeyIds)) {
inventories = inventoryMapper.queryInventoryByItemKeyId(itemKeyIds);
data.setInventories(inventories);
}
if (CollectionUtils.isNotEmpty(inventories)) {
//根据itemKeyId分组
Map<Long, List<Inventory>> inventoryMap = inventories.stream().collect(Collectors.groupingBy(Inventory::getItemKeyId));
data.setInventoryMap(inventoryMap);
//获取容器
List<Long> stockIds = inventories.stream().map(Inventory::getStockId).distinct().toList();
Map<Long, Stock> stockMap = stockService.queryByStockIdsToMap(stockIds);
data.setStockMap(stockMap);
//获取目标库位
List<Long> pointIds = inventories.stream().map(Inventory::getPointId).distinct().toList();
Map<Long, Point> pointMap = pointService.queryByPointIdsToMap(pointIds);
data.setPointMap(pointMap);
}
return data;
}
/**
*
*
* @param data AllocationData
* @param errorMsgSet
*/
private boolean validateInventory(AllocationData data, Set<String> errorMsgSet) {
List<Inventory> inventories = data.getInventories();
if (CollectionUtils.isEmpty(inventories)) {
String itemCodes = data.getItemMap().values().stream().map(Item::getItemCode).collect(Collectors.joining(","));
errorMsgSet.add("【" + itemCodes + "】物料库存不足");
return false;
}
return true;
}
/**
*
*/
private void allocate(AllocationData data, Map<Long, Inventory> inventoryUpdateMap, Map<Long, PickDetail> pickDetailUpdateMap, List<Task> createToTask, Set<String> errorMsgSet) {
for (PickDetail pickDetail : data.getPickDetails()) {
try {
allocatePickDetail(pickDetail, data, errorMsgSet, inventoryUpdateMap, pickDetailUpdateMap, createToTask);
} catch (Exception e) {
log.error("分配明细失败明细ID: {}", pickDetail.getId(), e);
errorMsgSet.add(String.format("分配明细失败明细ID:%s原因:%s",
pickDetail.getId(), e.getMessage()));
}
}
}
/**
*
*
* @param pickDetail
* @param data
* @param errorMsgSet
* @param inventoryUpdateMap
* @param pickDetailUpdateMap
* @param createToTask
*/
private void allocatePickDetail(PickDetail pickDetail, AllocationData data,
Set<String> errorMsgSet, Map<Long, Inventory> inventoryUpdateMap,
Map<Long, PickDetail> pickDetailUpdateMap, List<Task> createToTask) {
Pick pick = data.getPickMap().get(pickDetail.getPickId());
Item item = data.getItemMap().get(pickDetail.getItemId());
// 计算未分配数量
BigDecimal unAllocatedQty = BigDecimalUtil.subtract(pickDetail.getOrderQty(), pickDetail.getAllocatedQty(), 0);
if (unAllocatedQty.compareTo(BigDecimal.ZERO) <= 0) {
return; // 已全部分配,无需处理
}
// 查找匹配库存
List<Inventory> matchedInventories = findMatchedInventories(pickDetail, pick, data);
if (CollectionUtils.isEmpty(matchedInventories)) {
addInventoryNotFoundErrorMsg(pick, pickDetail, item, errorMsgSet);
return;
}
String lockKey = String.valueOf(pickDetail.getId());
String lockValue = null;
try {
lockValue = redissonLock.tryLock(lockKey, 10);
if (StringUtils.isEmpty(lockValue)) {
return;
}
// 分配库存
allocateInventory(pickDetail, item, pick, matchedInventories, inventoryUpdateMap, pickDetailUpdateMap,
createToTask, data, unAllocatedQty, errorMsgSet);
} finally {
if (StringUtils.isNotEmpty(lockValue)) {
redissonLock.unlock(lockKey, lockValue);
}
}
}
/**
*
*
* @param pickDetail
* @param pick
* @param data
* @return
*/
private List<Inventory> findMatchedInventories(PickDetail pickDetail,
Pick pick, AllocationData data) {
ItemGroupKey groupKey = ItemGroupKey.of(
pickDetail.getItemId(),
pick.getWhCode(),
pickDetail.getProject(),
pickDetail.getTaskNo(),
pickDetail.getPropC1(),
pickDetail.getPropC3()
);
ItemKey itemKey = data.getItemGroupMap().get(groupKey);
return data.getInventoryMap().get(itemKey.getId());
}
/**
*
*
* @param pickDetail
* @param item
* @param pick
* @param errorMsgSet
*/
private void addInventoryNotFoundErrorMsg(Pick pick, PickDetail pickDetail, Item item,
Set<String> errorMsgSet) {
errorMsgSet.add(String.format("物料【%s】无匹配库存物料ID:%s批次:%s库存状态:%s仓库:%s",
item.getItemCode(), item.getId(),
pickDetail.getPropC1(), pickDetail.getPropC3(), pick.getWhCode()));
}
/**
*
*
* @param pickDetail
* @param item
* @param pick
* @param matchedInventories
* @param inventoryUpdateMap
* @param pickDetailUpdateMap
* @param createToTask
* @param data
* @param totalUnAllocatedQty
*/
private void allocateInventory(PickDetail pickDetail, Item item, Pick pick,
List<Inventory> matchedInventories, Map<Long, Inventory> inventoryUpdateMap,
Map<Long, PickDetail> pickDetailUpdateMap, List<Task> createToTask,
AllocationData data, BigDecimal totalUnAllocatedQty, Set<String> errorMsgSet) {
// 智能排序库存
List<InventoryScore> scoredInventories = scoreInventories(matchedInventories);
//未分配数量
BigDecimal remainingQty = totalUnAllocatedQty;
String requestId = UUID.randomUUID().toString();
for (InventoryScore inventoryScore : scoredInventories) {
if (remainingQty.compareTo(BigDecimal.ZERO) <= 0) {
break;
}
Inventory inventory = inventoryScore.getInventory();
// 库存可用数量
BigDecimal availableQty = BigDecimalUtil.subtract(inventory.getQuantity(), inventory.getQueuedQty(), 0);
if (availableQty.compareTo(BigDecimal.ZERO) <= 0) {
continue;
}
//本次分配数量(取最小值)
BigDecimal allocateQty = remainingQty.min(availableQty);
// 更新库存
updateInventoryAllocation(inventory, allocateQty, inventoryUpdateMap);
// 更新拣货明细
updatePickDetailAllocation(pickDetail, allocateQty, pickDetailUpdateMap);
// 创建任务
createPickTask(pickDetail, pick, item, inventory, inventoryScore, allocateQty, createToTask, data);
// 记录分配日志
inventoryLogService.addAllocInventoryLog(inventory, inventoryScore.getOutPoint().getId(), allocateQty, pick.getOrderNo(), pickDetail.getId(), pickDetail.getDescription());
// 更新剩余未分配数量
remainingQty = BigDecimalUtil.subtract(remainingQty, allocateQty, 0);
}
//最后还有未分配数量,添加错误信息
if (remainingQty.compareTo(BigDecimal.ZERO) > 0) {
addInventoryErrorMsg(pick, pickDetail, item, remainingQty, 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 = pointService.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 = pointService.findByColAndLayer(point.getColNum(), point.getLayerNum());
colLayerPointsMap.put(key, points);
}
//获取出库口的库位
List<Point> outPoints = pointService.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()
.thenComparing(score -> score.getMovePoints().size())
)
.toList();
}
/**
*
*
* @param inventory
* @param currPoint
* @param points
* @return
*/
private InventoryScore calculateMoveCount(Inventory inventory, Point currPoint, List<Point> points, List<Point> outPoints) {
// 位移分数
double moveScore;
//移位库位
List<Point> movePoints;
// 计算距离分数权重30%
Point bestPoint = getBestOutboundPoint(currPoint, outPoints);
double distanceScore = calculateClusterDistanceCost(currPoint, bestPoint) * 0.3;
// 目标库位的深度位转换为索引
int targetIndex = Integer.parseInt(currPoint.getRowNum()) - 1;
//双通道
if (currPoint.getIzDoubleLane().equals(1)) {
// 计算左侧占用数
List<Point> leftPoints = calculateUsedPoints(points, 0, targetIndex);
// 计算右侧占用数
List<Point> rightPoints = calculateUsedPoints(points, targetIndex + 1, points.size());
//取两个集合中,元素最少的那个集合
movePoints = leftPoints.size() < rightPoints.size() ? leftPoints : rightPoints;
moveScore = (100.0 / (movePoints.size() + 1)) * 0.7;
} else {
movePoints = calculateUsedPoints(points, 0, targetIndex);
moveScore = (100.0 / (movePoints.size() + 1)) * 0.7;
}
//库位得分 = 距离得分 + 移动得分
double totalScore = distanceScore + moveScore;
log.info("【{}】库位距离评分: {}-移位评分: {}-总得分: {}-移位次数: {}",
currPoint.getPointCode(), distanceScore, moveScore, totalScore, movePoints.size());
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));
}
/**
* 使
*
* @param points
* @param start
* @param end
* @return List<Point>
*/
private List<Point> calculateUsedPoints(List<Point> points, int start, int end) {
List<Point> usedPoints = new ArrayList<>();
for (int i = start; i < end && i < points.size(); i++) {
Point point = points.get(i);
if (point != null && point.getStatus().equals(CommonStatusEnum.USED.getValue())) {
usedPoints.add(point);
}
}
return usedPoints;
}
/**
*
*
* @param inventory
* @param allocateQty
* @param inventoryUpdateMap
*/
private void updateInventoryAllocation(Inventory inventory, BigDecimal allocateQty,
Map<Long, Inventory> inventoryUpdateMap) {
Inventory inventoryToUpdate = inventoryUpdateMap.computeIfAbsent(
inventory.getId(), id -> {
// 2. 用Builder实现拷贝
return Inventory.builder()
.id(inventory.getId())
.itemId(inventory.getItemId())
.itemKeyId(inventory.getItemKeyId())
.pointId(inventory.getPointId())
.stockId(inventory.getStockId())
.quantity(inventory.getQuantity())
.queuedQty(BigDecimal.ZERO)
.receiveRecordId(inventory.getReceiveRecordId())
.status(inventory.getStatus())
.description(inventory.getDescription())
.sysOrgCode(inventory.getSysOrgCode())
.tenantId(inventory.getTenantId())
.createBy(inventory.getCreateBy())
.createTime(inventory.getCreateTime())
.build();
});
BigDecimal newQueuedQty = BigDecimalUtil.add(inventoryToUpdate.getQueuedQty(), allocateQty, 0);
inventoryToUpdate.setQueuedQty(newQueuedQty);
inventoryToUpdate.setStatus(InventoryStatusEnum.ALLOCATED.getValue());
}
/**
*
*
* @param pickDetail
* @param allocateQty
* @param pickDetailUpdateMap
*/
private void updatePickDetailAllocation(PickDetail pickDetail, BigDecimal allocateQty,
Map<Long, PickDetail> pickDetailUpdateMap) {
PickDetail detailToUpdate = pickDetailUpdateMap.computeIfAbsent(
pickDetail.getId(), id -> {
// 2. 用Builder实现拷贝
return PickDetail.builder()
.id(pickDetail.getId())
.pickId(pickDetail.getPickId())
.itemId(pickDetail.getItemId())
.lineNo(pickDetail.getLineNo())
.unit(pickDetail.getUnit())
.project(pickDetail.getProject())
.taskNo(pickDetail.getTaskNo())
.orderQty(pickDetail.getOrderQty())
.allocatedQty(BigDecimal.ZERO)
.pickedQty(pickDetail.getPickedQty())
.status(pickDetail.getStatus())
.propC1(pickDetail.getPropC1())
.propC3(pickDetail.getPropC3())
.description(pickDetail.getDescription())
.tenantId(pickDetail.getTenantId())
.sysOrgCode(pickDetail.getSysOrgCode())
.createBy(pickDetail.getCreateBy())
.createTime(pickDetail.getCreateTime())
.build();
});
BigDecimal newAllocatedQty = BigDecimalUtil.add(detailToUpdate.getAllocatedQty(), allocateQty, 0);
detailToUpdate.setAllocatedQty(newAllocatedQty);
// 更新状态
Integer status = newAllocatedQty.compareTo(detailToUpdate.getOrderQty()) >= 0 ?
PickStatusEnum.ASSIGNED.getValue() : PickStatusEnum.PARTIAL.getValue();
detailToUpdate.setStatus(status);
}
/**
*
*
* @param pickDetail
* @param pick
* @param item
* @param inventory
* @param inventoryScore
* @param allocateQty
* @param createToTask
* @param data
*/
private void createPickTask(PickDetail pickDetail, Pick pick, Item item,
Inventory inventory, InventoryScore inventoryScore, BigDecimal allocateQty,
List<Task> createToTask, AllocationData data) {
Stock stock = data.getStockMap().get(inventory.getStockId());
Point fromPoint = data.getPointMap().get(inventory.getPointId());
// 判断是否整托分配
BigDecimal originalAvailableQty = BigDecimalUtil.subtract(inventory.getQuantity(), inventory.getQueuedQty(), 0);
Integer izAll = originalAvailableQty.compareTo(allocateQty) == 0 ? 0 : 1;
// 目标库位
Point toPoint = inventoryScore.getOutPoint();
// 构建拣货任务
String taskNo = String.format("%s_%d", pick.getNo(), pickDetail.getLineNo());
Task task = taskService.bulidTask(
taskNo, TaskTypeEnum.PICK.getValue(), item, fromPoint, toPoint,
stock, pick.getId(), pickDetail.getId(), inventory.getItemKeyId(), inventory.getId(),
allocateQty, izAll);
if (task != null) {
createToTask.add(task);
log.info("生成拣货任务: {}- 容器:{} - 库位:{} - 类型:{}- 分配数量:{}",
task.getTaskNo(), stock.getStockCode(),
fromPoint.getPointCode(), izAll, allocateQty);
}
}
/**
*
*
* @param pickDetail
* @param item
* @param pick
* @param errorMsgSet
*/
private void addInventoryErrorMsg(Pick pick, PickDetail pickDetail, Item item,
BigDecimal unallocateQty, Set<String> errorMsgSet) {
BigDecimal orderQty = pickDetail.getOrderQty();
BigDecimal allocatedQty = BigDecimalUtil.subtract(orderQty, unallocateQty, 0);
String failInfo = String.format(
"物料【%s】库存不足任务号:%s行号:%d需求:%s已分配:%s缺货:%s",
item.getItemCode(),
pick.getNo(),
pickDetail.getLineNo(),
orderQty,
allocatedQty,
unallocateQty
);
errorMsgSet.add(failInfo);
}
/**
*
*
* @param inventoryUpdateMap
* @param pickDetailUpdateMap
* @param createToTask
*/
@Transactional(rollbackFor = Exception.class)
public void batchOperation(Map<Long, Inventory> inventoryUpdateMap, Map<Long, PickDetail> pickDetailUpdateMap, List<Task> createToTask) {
List<Inventory> updateToInventory = new ArrayList<>(inventoryUpdateMap.values());
List<PickDetail> updateToPickDetail = new ArrayList<>(pickDetailUpdateMap.values());
if (CollectionUtils.isNotEmpty(updateToInventory)) {
batchUtil.updateBatchInventory(updateToInventory);
}
if (CollectionUtils.isNotEmpty(updateToPickDetail)) {
batchUtil.updateBatchPickDetail(updateToPickDetail);
}
if (CollectionUtils.isNotEmpty(createToTask)) {
batchUtil.saveBatchTask(createToTask);
}
}
/**
*
*
* @param data
*/
private void refreshData(AllocationData data) {
for (Map.Entry<Long, Pick> entry : data.getPickMap().entrySet()) {
pickDetailService.refreshPick(entry.getValue(), pickDetailMapper.selectByMainId(entry.getKey()));
}
}
}

View File

@ -0,0 +1,297 @@
package org.cpte.modules.shipping.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.cpte.modules.constant.enums.InventoryStatusEnum;
import org.cpte.modules.constant.enums.PickStatusEnum;
import org.cpte.modules.inventory.entity.Inventory;
import org.cpte.modules.inventory.service.IInventoryService;
import org.cpte.modules.inventoryLog.service.IInventoryLogService;
import org.cpte.modules.shipping.entity.Pick;
import org.cpte.modules.shipping.entity.PickDetail;
import org.cpte.modules.shipping.entity.Task;
import org.cpte.modules.shipping.mapper.PickDetailMapper;
import org.cpte.modules.shipping.mapper.TaskMapper;
import org.cpte.modules.shipping.vo.CancelAllocateData;
import org.cpte.modules.utils.BatchUtil;
import org.cpte.modules.utils.BigDecimalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
/**
*
*/
@Service
@Slf4j
public class CancelAllocateProcessor {
@Autowired
private PickDetailMapper pickDetailMapper;
@Autowired
private TaskMapper taskMapper;
@Autowired
private IPickDetailService pickDetailService;
@Autowired
private IInventoryService inventoryService;
@Autowired
private IInventoryLogService inventoryLogService;
@Autowired
private BatchUtil batchUtils;
/**
*
*
* @param pickIds ID
* @return
*/
public List<String> cancelAllocatePick(List<Long> pickIds) {
// 错误信息去重LinkedHashSet保证顺序和唯一
Set<String> errorMsgSet = new LinkedHashSet<>();
// 1.数据准备
CancelAllocateData data = prepareCancelAllocateData(pickIds);
//2.创建数据结构
Map<Long, Inventory> inventoryUpdateMap = new HashMap<>();
Map<Long, PickDetail> pickDetailUpdateMap = new HashMap<>();
List<Task> deleteToTask = new ArrayList<>();
//3.取消分配
cancelAllocate(data, inventoryUpdateMap, pickDetailUpdateMap, deleteToTask, errorMsgSet);
//4.批量操作
batchOperation(inventoryUpdateMap, pickDetailUpdateMap, deleteToTask);
//5.刷新出库单
refreshData(data);
return new ArrayList<>(errorMsgSet);
}
/**
*
*
* @param pickIds ID
* @return CancelAllocateData
*/
private CancelAllocateData prepareCancelAllocateData(List<Long> pickIds) {
CancelAllocateData data = new CancelAllocateData();
//查询出库单
Map<Long, Pick> pickMap = pickDetailService.queryByPickIdsToMap(pickIds);
data.setPickMap(pickMap);
//查询出库单明细
List<PickDetail> pickDetails = pickDetailMapper.queryByPickIds(pickIds);
data.setPickDetails(pickDetails);
//pickDetails 转成 pickDetailId -> pickDetail
Map<Long, PickDetail> pickDetailIdMap = pickDetails.stream().collect(Collectors.toMap(PickDetail::getId, PickDetail -> PickDetail));
data.setPickDetailIdMap(pickDetailIdMap);
List<Task> tasks = taskMapper.queryByPickIds(pickIds);
data.setTasks(tasks);
List<Long> inventoryIds = tasks.stream().map(Task::getInventoryId).distinct().toList();
Map<Long, Inventory> inventoryMap = inventoryService.queryByInventoryIdsToMap(inventoryIds);
data.setInventoryMap(inventoryMap);
return data;
}
/**
*
*
* @param data
* @param inventoryUpdateMap
* @param pickDetailUpdateMap
* @param deleteToTask Task
* @param errorMsgSet
*/
private void cancelAllocate(CancelAllocateData data, Map<Long, Inventory> inventoryUpdateMap, Map<Long, PickDetail> pickDetailUpdateMap, List<Task> deleteToTask, Set<String> errorMsgSet) {
for (Task task : data.getTasks()) {
try {
cancelAllocateTask(task, data, inventoryUpdateMap, pickDetailUpdateMap, deleteToTask, errorMsgSet);
} catch (Exception e) {
log.error("取消任务失败任务ID: {}", task.getId(), e);
errorMsgSet.add(String.format("取消任务失败,任务号:%s原因:%s",
task.getTaskNo(), e.getMessage()));
}
}
}
/**
*
*
* @param task
* @param data
* @param inventoryUpdateMap
* @param pickDetailUpdateMap
* @param deleteToTask
* @param errorMsgSet
*/
private void cancelAllocateTask(Task task, CancelAllocateData data, Map<Long, Inventory> inventoryUpdateMap,
Map<Long, PickDetail> pickDetailUpdateMap, List<Task> deleteToTask, Set<String> errorMsgSet) {
Pick pick = data.getPickMap().get(task.getPickId());
PickDetail pickDetail = data.getPickDetailIdMap().get(task.getPickDetailId());
if (task.getAgvTaskId() != null) {
errorMsgSet.add(String.format("取消失败:【%s】已生成AGV任务", task.getTaskNo()));
return;
}
Inventory inventory = data.getInventoryMap().get(task.getInventoryId());
if (inventory == null) {
errorMsgSet.add(String.format("取消失败:【%s】库存不存在", task.getTaskNo()));
return;
}
if (inventory.getQueuedQty().compareTo(BigDecimal.ZERO) <= 0) {
errorMsgSet.add(String.format("取消失败:【%s】库存已取消分配", task.getTaskNo()));
return;
}
BigDecimal cancelQty = task.getPlanQty();
//更新库存
updateInventoryAllocation(inventory, cancelQty, inventoryUpdateMap);
//更新出库明细
updatePickDetailAllocation(pickDetail, cancelQty, pickDetailUpdateMap);
//删除任务
deleteToTask.add(task);
// 记录取消分配日志
inventoryLogService.addUnAllocInventoryLog(inventory, cancelQty, pick.getOrderNo(), pickDetail.getId(), pickDetail.getDescription());
}
/**
*
*
* @param inventory
* @param cancelQty
* @param inventoryUpdateMap
*/
private void updateInventoryAllocation(Inventory inventory, BigDecimal cancelQty,
Map<Long, Inventory> inventoryUpdateMap) {
Inventory inventoryToUpdate = inventoryUpdateMap.computeIfAbsent(
inventory.getId(), id -> {
// 2. 用Builder实现拷贝
return Inventory.builder()
.id(inventory.getId())
.itemId(inventory.getItemId())
.itemKeyId(inventory.getItemKeyId())
.pointId(inventory.getPointId())
.stockId(inventory.getStockId())
.quantity(inventory.getQuantity())
.queuedQty(inventory.getQueuedQty())
.receiveRecordId(inventory.getReceiveRecordId())
.status(inventory.getStatus())
.description(inventory.getDescription())
.sysOrgCode(inventory.getSysOrgCode())
.tenantId(inventory.getTenantId())
.createBy(inventory.getCreateBy())
.createTime(inventory.getCreateTime())
.build();
});
BigDecimal newCancelQty = BigDecimalUtil.subtract(inventoryToUpdate.getQueuedQty(), cancelQty, 0);
inventoryToUpdate.setQueuedQty(newCancelQty);
Integer inv_status = inventoryToUpdate.getQueuedQty().compareTo(BigDecimal.ZERO) > 0 ? InventoryStatusEnum.ALLOCATED.getValue() : InventoryStatusEnum.AVAILABLE.getValue();
inventoryToUpdate.setStatus(inv_status);
}
/**
*
*
* @param pickDetail
* @param cancelQty
* @param pickDetailUpdateMap
*/
private void updatePickDetailAllocation(PickDetail pickDetail, BigDecimal cancelQty,
Map<Long, PickDetail> pickDetailUpdateMap) {
PickDetail detailToUpdate = pickDetailUpdateMap.computeIfAbsent(
pickDetail.getId(), id -> {
// 2. 用Builder实现拷贝
return PickDetail.builder()
.id(pickDetail.getId())
.pickId(pickDetail.getPickId())
.itemId(pickDetail.getItemId())
.lineNo(pickDetail.getLineNo())
.unit(pickDetail.getUnit())
.project(pickDetail.getProject())
.taskNo(pickDetail.getTaskNo())
.orderQty(pickDetail.getOrderQty())
.allocatedQty(pickDetail.getAllocatedQty())
.pickedQty(pickDetail.getPickedQty())
.status(pickDetail.getStatus())
.propC1(pickDetail.getPropC1())
.propC3(pickDetail.getPropC3())
.description(pickDetail.getDescription())
.tenantId(pickDetail.getTenantId())
.sysOrgCode(pickDetail.getSysOrgCode())
.createBy(pickDetail.getCreateBy())
.createTime(pickDetail.getCreateTime())
.build();
});
BigDecimal newCancelQty = BigDecimalUtil.subtract(detailToUpdate.getAllocatedQty(), cancelQty, 0);
detailToUpdate.setAllocatedQty(newCancelQty);
// 更新状态
Integer status=PickStatusEnum.CREATED.getValue();
BigDecimal allocateQty = detailToUpdate.getAllocatedQty();
if (detailToUpdate.getOrderQty().compareTo(allocateQty)<=0) {
status = PickStatusEnum.ASSIGNED.getValue();
} else if(detailToUpdate.getOrderQty().compareTo(allocateQty)>0 && allocateQty.compareTo(BigDecimal.ZERO)>0){
status = PickStatusEnum.PARTIAL.getValue();
}
detailToUpdate.setStatus(status);
}
/**
*
*
* @param inventoryUpdateMap
* @param pickDetailUpdateMap
* @param deleteToTask
*/
@Transactional(rollbackFor = Exception.class)
public void batchOperation(Map<Long, Inventory> inventoryUpdateMap, Map<Long, PickDetail> pickDetailUpdateMap, List<Task> deleteToTask) {
List<Inventory> updateToInventory = new ArrayList<>(inventoryUpdateMap.values());
List<PickDetail> updateToPickDetail = new ArrayList<>(pickDetailUpdateMap.values());
if (CollectionUtils.isNotEmpty(updateToInventory)) {
batchUtils.updateBatchInventory(updateToInventory);
}
if (CollectionUtils.isNotEmpty(updateToPickDetail)) {
batchUtils.updateBatchPickDetail(updateToPickDetail);
}
if (CollectionUtils.isNotEmpty(deleteToTask)) {
taskMapper.deleteByIds(deleteToTask);
}
}
/**
*
*
* @param data
*/
private void refreshData(CancelAllocateData data) {
for (Map.Entry<Long, Pick> entry : data.getPickMap().entrySet()) {
pickDetailService.refreshPick(entry.getValue(), pickDetailMapper.selectByMainId(entry.getKey()));
}
}
}

View File

@ -1,8 +1,10 @@
package org.cpte.modules.shipping.service;
import org.cpte.modules.shipping.entity.Pick;
import org.cpte.modules.shipping.entity.PickDetail;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import java.util.Map;
/**
* @Description:
@ -19,4 +21,22 @@ public interface IPickDetailService extends IService<PickDetail> {
* @return List<PickDetail>
*/
public List<PickDetail> selectByMainId(Long mainId);
/**
* IDMap
*
* @param pickIds ID
* @return Map<Long, Pick>
*/
Map<Long, Pick> queryByPickIdsToMap(List<Long> pickIds);
/**
*
*
* @param pick
* @param pickDetails
*/
void refreshPick(Pick pick, List<PickDetail> pickDetails);
}

View File

@ -62,12 +62,13 @@ public interface IPickService extends IService<Pick> {
/**
*
*
* @param details
* @param details
* @param exitItemMap
* @return
*/
List<PickDetail> buildPickDetail(List<OutboundRequest.OutboundDetail> details, Map<String, Item> exitItemMap);
/**
*
*
@ -75,7 +76,6 @@ public interface IPickService extends IService<Pick> {
*/
List<String> allocatePick(List<Long> pickIds);
/**
*
*

View File

@ -50,7 +50,7 @@ public interface ITaskService extends IService<Task> {
* @param izAll
* @return Task
*/
Task bulidTask(String taskNo, Integer taskType, Item item, Point fromPoint, Point toPoint, Stock stock, Long pickId, Long pickDetailId, Long inventoryId, BigDecimal planQty, Integer izAll);
Task bulidTask(String taskNo, Integer taskType, Item item, Point fromPoint, Point toPoint, Stock stock, Long pickId, Long pickDetailId,Long itemKeyId, Long inventoryId, BigDecimal planQty, Integer izAll);
/**
* TaskAGV
@ -65,4 +65,5 @@ public interface ITaskService extends IService<Task> {
*/
List<Task> bulidMoveTask(List<Long> movePointIds);
List<Task> queryTaskByMainId(Long id);
}

View File

@ -1,24 +1,109 @@
package org.cpte.modules.shipping.service.impl;
import org.apache.commons.collections4.CollectionUtils;
import org.cpte.modules.constant.enums.PickStatusEnum;
import org.cpte.modules.shipping.entity.Pick;
import org.cpte.modules.shipping.entity.PickDetail;
import org.cpte.modules.shipping.mapper.PickDetailMapper;
import org.cpte.modules.shipping.mapper.PickMapper;
import org.cpte.modules.shipping.service.IPickDetailService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.math.BigDecimal;
import java.util.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @Description:
* @author: cpte
* @Date: 2025-11-14
* @Date: 2025-11-14
* @Version: V1.0
*/
@Service
public class PickDetailServiceImpl extends ServiceImpl<PickDetailMapper, PickDetail> implements IPickDetailService {
@Override
public List<PickDetail> selectByMainId(Long mainId) {
return this.baseMapper.selectByMainId(mainId);
@Autowired
private PickMapper pickMapper;
@Override
public List<PickDetail> selectByMainId(Long mainId) {
return this.baseMapper.selectByMainId(mainId);
}
/**
* Map
*
* @param pickIds id
* @return Map
*/
public Map<Long, Pick> queryByPickIdsToMap(List<Long> pickIds) {
if (CollectionUtils.isEmpty(pickIds)) {
return Collections.emptyMap();
}
Map<Long, Pick> pickMap = new HashMap<>();
List<Pick> pickList = pickMapper.selectByIds(pickIds);
for (Pick pick : pickList) {
pickMap.put(pick.getId(), pick);
}
return pickMap;
}
/**
*
*
* @param pick
* @param pickDetails
*/
public void refreshPick(Pick pick, List<PickDetail> pickDetails) {
if (pickDetails == null) {
pickDetails = new ArrayList<>();
}
// 计算各种数量
BigDecimal orderQty = pickDetails.stream().map(PickDetail::getOrderQty).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal allocatedQty = pickDetails.stream().map(PickDetail::getAllocatedQty).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal pickedQty = pickDetails.stream().map(PickDetail::getPickedQty).reduce(BigDecimal.ZERO, BigDecimal::add);
// 当前状态
Integer status = pick.getStatus();
// 如果没有任何需求,则为创建状态
if (orderQty.compareTo(BigDecimal.ZERO) <= 0) {
status = PickStatusEnum.CREATED.getValue();
}
// 如果分配数量为0但有需求数量则为创建状态
else if (allocatedQty.compareTo(BigDecimal.ZERO) == 0) {
status = PickStatusEnum.CREATED.getValue();
}
// 如果已全部分配但未开始拣货
else if (allocatedQty.compareTo(orderQty) >= 0 && pickedQty.compareTo(BigDecimal.ZERO) == 0) {
status = PickStatusEnum.ASSIGNED.getValue();
}
// 如果部分分配且未开始拣货
else if (allocatedQty.compareTo(orderQty) < 0 && allocatedQty.compareTo(BigDecimal.ZERO) > 0 && pickedQty.compareTo(BigDecimal.ZERO) == 0) {
status = PickStatusEnum.PARTIAL.getValue();
}
// 如果开始拣货但未完成
else if (pickedQty.compareTo(BigDecimal.ZERO) > 0 && pickedQty.compareTo(orderQty) < 0) {
status = PickStatusEnum.PICKING.getValue();
}
// 如果已完成拣货
else if (pickedQty.compareTo(orderQty) >= 0) {
status = PickStatusEnum.PICKED.getValue();
}
//明细的状态都是已关闭则出库单状态为已关闭
boolean isClosed = pickDetails.stream().allMatch(detail -> PickStatusEnum.CLOSED.getValue().equals(detail.getStatus()));
if (isClosed) {
status = PickStatusEnum.CLOSED.getValue();
}
// 更新实体属性
pick.setOrderQty(orderQty);
pick.setAllocatedQty(allocatedQty);
pick.setPickedQty(pickedQty);
pick.setStatus(status);
pickMapper.updateById(pick);
}
}

View File

@ -7,22 +7,17 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.cpte.modules.base.entity.ItemKey;
import org.cpte.modules.base.mapper.ItemKeyMapper;
import org.cpte.modules.constant.GeneralConstant;
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.*;
import org.cpte.modules.inventory.entity.Inventory;
import org.cpte.modules.inventory.mapper.InventoryMapper;
import org.cpte.modules.inventory.service.IInventoryService;
import org.cpte.modules.inventoryLog.service.IInventoryLogService;
import org.cpte.modules.receive.entity.Asn;
import org.cpte.modules.receive.entity.AsnDetail;
import org.cpte.modules.saiWms.request.OutboundRequest;
import org.cpte.modules.saiWms.request.SaiWmsRequest;
import org.cpte.modules.saiWms.request.SMOMRequest;
import org.cpte.modules.serialNumber.PickSerialNumberRule;
import org.cpte.modules.shipping.entity.Pick;
import org.cpte.modules.shipping.entity.PickDetail;
@ -30,13 +25,13 @@ import org.cpte.modules.shipping.entity.Task;
import org.cpte.modules.shipping.mapper.PickDetailMapper;
import org.cpte.modules.shipping.mapper.PickMapper;
import org.cpte.modules.shipping.mapper.TaskMapper;
import org.cpte.modules.shipping.service.AllocateProcessor;
import org.cpte.modules.shipping.service.CancelAllocateProcessor;
import org.cpte.modules.shipping.service.IPickDetailService;
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.cpte.modules.utils.HttpPostUtil;
import org.cpte.modules.utils.RedisDistributedLockUtil;
import org.cpte.modules.utils.SwmsLoginUtil;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.vo.LoginUser;
@ -53,7 +48,6 @@ import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* @Description:
@ -70,51 +64,29 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
@Autowired
private TaskMapper taskMapper;
@Autowired
private ItemKeyMapper itemKeyMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private SysDictMapper sysDictMapper;
@Autowired
private OpenApiMapper openApiMapper;
@Autowired
private IItemService iItemService;
@Autowired
private IStockService iStockService;
@Autowired
private IPointService iPointService;
@Autowired
private ITaskService iTaskService;
private IPickDetailService pickDetailService;
@Autowired
private IInventoryService inventoryService;
@Autowired
private IInventoryLogService iInventoryLogService;
@Autowired
private SwmsLoginUtil swmsLoginUtil;
private IInventoryLogService inventoryLogService;
@Autowired
private BaseCommonService baseCommonService;
@Autowired
private SwmsLoginUtil swmsLoginUtil;
@Autowired
private BatchUtil batchUtils;
@Autowired
private PickSerialNumberRule pickSerialNumberRule;
/**
* Map
*
* @param pickIds id
* @return Map
*/
private Map<Long, Pick> queryByPickIdsToMap(List<Long> pickIds) {
if (CollectionUtils.isEmpty(pickIds)) {
return Collections.emptyMap();
}
Map<Long, Pick> pickMap = new HashMap<>();
List<Pick> pickList = this.baseMapper.selectByIds(pickIds);
for (Pick pick : pickList) {
pickMap.put(pick.getId(), pick);
}
return pickMap;
}
@Autowired
private AllocateProcessor allocateProcessor;
@Autowired
private CancelAllocateProcessor cancelAllocateProcessor;
@Autowired
private RedisDistributedLockUtil redissonLock;
/**
* Map
@ -134,25 +106,37 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
return pickDetailMap;
}
private Map<Long, PickDetail> queryPickDetailByPickIds(List<Long> pickIds) {
if (CollectionUtils.isEmpty(pickIds)) {
return Collections.emptyMap();
}
List<PickDetail> pickDetails = pickDetailMapper.queryByPickIds(pickIds);
Map<Long, PickDetail> pickDetailMap = new HashMap<>();
for (PickDetail pickDetail : pickDetails) {
pickDetailMap.put(pickDetail.getId(), pickDetail);
}
return pickDetailMap;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveMain(Pick pick, List<PickDetail> pickDetailList) {
String lockKey = "pick:" + pick.getNo();
String lockValue = null;
try {
lockValue = redissonLock.tryLock(lockKey, 10);
if (StringUtils.isEmpty(lockValue)) {
throw new RuntimeException("出库处理中,请稍后重试");
}
processorSaveMain(pick, pickDetailList);
} finally {
if (StringUtils.isNotEmpty(lockValue)) {
redissonLock.unlock(lockKey, lockValue);
}
}
}
/**
*
*
* @param pick
* @param pickDetailList
*/
@Transactional(rollbackFor = Exception.class)
public void processorSaveMain(Pick pick, List<PickDetail> pickDetailList) {
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
pick.setTenantId(Long.parseLong(sysUser.getRelTenantIds()));
pick.setSysOrgCode(sysUser.getOrgCode());
String orderNo = pickSerialNumberRule.generateSerialNumber(GeneralConstant.PICK_ORDER_NO);
pick.setOrderNo(orderNo);
this.baseMapper.insert(pick);
if (pickDetailList == null || pickDetailList.isEmpty()) {
@ -173,67 +157,9 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
pickDetailMapper.insert(entity);
}
//刷新出库单
refreshPick(pick, pickDetailList);
pickDetailService.refreshPick(pick, pickDetailList);
}
/**
*
*
* @param pick
* @param pickDetails
*/
public synchronized void refreshPick(Pick pick, List<PickDetail> pickDetails) {
if (pickDetails == null) {
pickDetails = new ArrayList<>();
}
// 计算各种数量
BigDecimal orderQty = pickDetails.stream().map(PickDetail::getOrderQty).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal allocatedQty = pickDetails.stream().map(PickDetail::getAllocatedQty).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal pickedQty = pickDetails.stream().map(PickDetail::getPickedQty).reduce(BigDecimal.ZERO, BigDecimal::add);
// 当前状态
Integer status = pick.getStatus();
// 如果没有任何需求,则为创建状态
if (orderQty.compareTo(BigDecimal.ZERO) <= 0) {
status = PickStatusEnum.CREATED.getValue();
}
// 如果分配数量为0但有需求数量则为创建状态
else if (allocatedQty.compareTo(BigDecimal.ZERO) == 0) {
status = PickStatusEnum.CREATED.getValue();
}
// 如果已全部分配但未开始拣货
else if (allocatedQty.compareTo(orderQty) >= 0 && pickedQty.compareTo(BigDecimal.ZERO) == 0) {
status = PickStatusEnum.ASSIGNED.getValue();
}
// 如果部分分配且未开始拣货
else if (allocatedQty.compareTo(orderQty) < 0 && pickedQty.compareTo(BigDecimal.ZERO) == 0) {
status = PickStatusEnum.PARTIAL.getValue();
}
// 如果开始拣货但未完成
else if (pickedQty.compareTo(BigDecimal.ZERO) > 0 && pickedQty.compareTo(orderQty) < 0) {
status = PickStatusEnum.PICKING.getValue();
}
// 如果已完成拣货
else if (pickedQty.compareTo(orderQty) >= 0 && orderQty.compareTo(BigDecimal.ZERO) > 0) {
status = PickStatusEnum.PICKED.getValue();
}
//明细的状态都是已关闭则出库单状态为已关闭
boolean isClosed = pickDetails.stream().allMatch(detail -> PickStatusEnum.CLOSED.getValue().equals(detail.getStatus()));
log.info("出库单明细是否全部关闭:{}", isClosed);
if (isClosed) {
status = PickStatusEnum.CLOSED.getValue();
}
// 更新实体属性
pick.setOrderQty(orderQty);
pick.setAllocatedQty(allocatedQty);
pick.setPickedQty(pickedQty);
pick.setStatus(status);
this.baseMapper.updateById(pick);
}
@Override
@Transactional(rollbackFor = Exception.class)
@ -277,7 +203,7 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
}
// 刷新出库单状态
refreshPick(pick, pickDetailList);
pickDetailService.refreshPick(pick, pickDetailList);
}
@Override
@ -311,9 +237,7 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
@Override
public Pick buildPick(OutboundRequest outboundRequest) {
String orderNo = pickSerialNumberRule.generateSerialNumber(GeneralConstant.PICK_ORDER_NO);
return Pick.builder()
.orderNo(orderNo)
.thirdOrderNo(outboundRequest.getOrderNo())
.no(outboundRequest.getNo())
.whCode(outboundRequest.getWhCode())
@ -332,7 +256,7 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
.lineNo(Integer.parseInt(detail.getLineNo()))
.itemId(exitItemMap.get(detail.getItem()).getId())
.unit(detail.getUnit())
.orderQty(detail.getQty())
.orderQty(BigDecimal.valueOf(detail.getQty()))
.allocatedQty(BigDecimal.ZERO)
.pickedQty(BigDecimal.ZERO)
.status(PickStatusEnum.CREATED.getValue())
@ -347,400 +271,18 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
}
@Override
@Transactional(rollbackFor = Exception.class)
public List<String> allocatePick(List<Long> pickIds) {
// 错误信息去重LinkedHashSet保证顺序和唯一
Set<String> errorMsgSet = new LinkedHashSet<>();
// -------------------------- 1. 查询关联数据批量查询减少DB交互--------------------------
//查询出库单
Map<Long, Pick> pickMap = queryByPickIdsToMap(pickIds);
//查询出库单明细
List<PickDetail> pickDetails = pickDetailMapper.queryByPickIds(pickIds);
//获取物料
List<Long> itemIds = pickDetails.stream().map(PickDetail::getItemId).distinct().toList();
Map<Long, Item> itemMap = iItemService.queryByItemIdsToMap(itemIds);
//筛选查询库存的条件(非空去重)外部仓库、项目号、任务号、批次、外部库存状态
List<String> whCodeList = pickMap.values().stream().map(Pick::getWhCode).filter(StringUtils::isNotBlank).distinct().toList();
List<String> projectList = pickDetails.stream().map(PickDetail::getProject).filter(StringUtils::isNotBlank).distinct().toList();
List<String> taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).filter(StringUtils::isNotBlank).distinct().toList();
List<String> propC1List = pickDetails.stream().map(PickDetail::getPropC1).filter(StringUtils::isNotBlank).distinct().toList();
List<String> propC3List = pickDetails.stream().map(PickDetail::getPropC3).filter(StringUtils::isNotBlank).distinct().toList();
List<ItemKey> itemKeys = itemKeyMapper.queryItemKeys(itemIds, whCodeList, projectList, taskNoList, propC1List, propC3List);
List<Long> itemKeyIds = itemKeys.stream().map(ItemKey::getId).distinct().toList();
//查询库存
List<Inventory> inventories = inventoryMapper.queryInventoryByItemKeyId(itemKeyIds);
if (CollectionUtils.isEmpty(inventories)) {
String itemCodes = itemMap.values().stream().map(Item::getItemCode).collect(Collectors.joining(","));
errorMsgSet.add("【" + itemCodes + "】物料库存不足");
return new ArrayList<>(errorMsgSet);
}
//根据itemKeyId分组
Map<Long, List<Inventory>> inventoryMap = inventories.stream().collect(Collectors.groupingBy(Inventory::getItemKeyId));
//获取容器
List<Long> stockIds = inventories.stream().map(Inventory::getStockId).distinct().toList();
Map<Long, Stock> stockMap = iStockService.queryByStockIdsToMap(stockIds);
//获取目标库位
List<Long> pointIds = inventories.stream().map(Inventory::getPointId).distinct().toList();
Map<Long, Point> pointMap = iPointService.queryByPointIdsToMap(pointIds);
// -------------------------- 2. 库存分组排序(优化匹配效率)--------------------------
// 按「itemKeyId分组
Map<InventoryGroupKey, ItemKey> inventoryGroupMap = itemKeys.stream()
.collect(Collectors.toMap(
itemKey -> InventoryGroupKey.of(
itemKey.getItemId(),
itemKey.getWhCode(),
itemKey.getProject(),
itemKey.getTaskNo(),
itemKey.getPropC1(),
itemKey.getPropC3()
),
itemKey -> itemKey
));
// -------------------------- 3. 创建更新列表---------------------------
List<Inventory> updateToInventory = new ArrayList<>();
List<PickDetail> updateToPickDetail = new ArrayList<>();
List<Task> createToTask = new ArrayList<>();
// -------------------------- 4. 循环分配库存 -------------------
for (PickDetail pickDetail : pickDetails) {
//出库单
Pick pick = pickMap.get(pickDetail.getPickId());
//物料
Item item = itemMap.get(pickDetail.getItemId());
//未分配数量
BigDecimal unAllocatedQty = BigDecimalUtil.subtract(pickDetail.getOrderQty(), pickDetail.getAllocatedQty(), 0);
if (unAllocatedQty.compareTo(BigDecimal.ZERO) <= 0) {
continue;
}
//匹配目标库存通过分组key快速定位
InventoryGroupKey groupKey = InventoryGroupKey.of(
pickDetail.getItemId(),
pick.getWhCode(),
pickDetail.getProject(),
pickDetail.getTaskNo(),
pickDetail.getPropC1(),
pickDetail.getPropC3()
);
ItemKey itemKey = inventoryGroupMap.get(groupKey);
List<Inventory> matchedInventories = inventoryMap.get(itemKey.getId());
if (CollectionUtils.isEmpty(matchedInventories)) {
errorMsgSet.add(String.format("物料【%s】无匹配库存物料ID:%s批次:%s库存状态:%s仓库:%s",
item.getItemCode(), item.getId(), pickDetail.getPropC1(), pickDetail.getPropC3(), pick.getWhCode()));
continue;
}
//智能排序,优先分配距离近、移位最少的库位
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) {
continue;
}
//本次分配数量(取最小值)
BigDecimal allocateQty = unAllocatedQty.min(availableQty);
// 更新库存占用数量
inventory.setQueuedQty(BigDecimalUtil.add(inventory.getQueuedQty(), allocateQty, 0));
inventory.setStatus(InventoryStatusEnum.ALLOCATED.getValue());
updateToInventory.add(inventory);
// 更新明细分配数量和状态
BigDecimal fpQty = BigDecimalUtil.add(pickDetail.getAllocatedQty(), allocateQty, 0);
pickDetail.setAllocatedQty(fpQty);
Integer status = fpQty.compareTo(pickDetail.getOrderQty()) >= 0 ? PickStatusEnum.ASSIGNED.getValue() : PickStatusEnum.PARTIAL.getValue();
pickDetail.setStatus(status);
updateToPickDetail.add(pickDetail);
//容器
Stock stock = stockMap.get(inventory.getStockId());
//库位
Point fromPoint = pointMap.get(inventory.getPointId());
//是否整托:0整托、1拆托
Integer izAll = availableQty.compareTo(allocateQty) == 0 ? 0 : 1;
//目标库位
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())) {
List<Long> movePointIds = inventoryScore.getMovePoints().stream().map(Point::getId).toList();
createToTask.addAll(iTaskService.bulidMoveTask(movePointIds));
} else {
log.info("无移位任务");
}
//分配库存日志
iInventoryLogService.addAllocInventoryLog(inventory, toPoint.getId(), allocateQty, pick.getOrderNo(), pickDetail.getId(), pickDetail.getDescription());
//更新未分配数量
unAllocatedQty = BigDecimalUtil.subtract(unAllocatedQty, allocateQty, 0);
}
//分配后仍有缺货,记录错误
if (unAllocatedQty.compareTo(BigDecimal.ZERO) > 0) {
String failInfo = String.format(
"物料【%s】库存不足任务号:%s行号:%d需求:%s已分配:%s缺货:%s",
item.getItemCode(),
pick.getNo(),
pickDetail.getLineNo(),
pickDetail.getOrderQty(),
BigDecimalUtil.subtract(pickDetail.getOrderQty(), unAllocatedQty, 0),
unAllocatedQty
);
errorMsgSet.add(failInfo);
}
}
// -------------------------- 5. 批量更新减少DB交互--------------------------
if (CollectionUtils.isNotEmpty(updateToInventory)) {
batchUtils.updateBatchInventory(updateToInventory);
}
if (CollectionUtils.isNotEmpty(updateToPickDetail)) {
batchUtils.updateBatchPickDetail(updateToPickDetail);
}
if (CollectionUtils.isNotEmpty(createToTask)) {
batchUtils.saveBatchTask(createToTask);
}
// -------------------------- 6. 刷新出库单状态 --------------------------
for (Pick pick : pickMap.values()) {
refreshPick(pick, pickDetailMapper.selectByMainId(pick.getId()));
}
return new ArrayList<>(errorMsgSet);
return allocateProcessor.allocatePick(pickIds);
}
/**
*
*
* @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()
.thenComparing(score -> score.getMovePoints().size())
)
.toList();
}
/**
*
*
* @param inventory
* @param currPoint
* @param points
* @return
*/
private InventoryScore calculateMoveCount(Inventory inventory, Point currPoint, List<Point> points, List<Point> outPoints) {
// 位移分数
double moveScore;
//移位库位
List<Point> movePoints;
// 计算距离分数权重30%
Point bestPoint = getBestOutboundPoint(currPoint, outPoints);
double distanceScore = calculateClusterDistanceCost(currPoint, bestPoint) * 0.3;
// 目标库位的深度位转换为索引
int targetIndex = Integer.parseInt(currPoint.getRowNum()) - 1;
//双通道
if (currPoint.getIzDoubleLane().equals(1)) {
// 计算左侧占用数
List<Point> leftPoints = calculateUsedPoints(points, 0, targetIndex);
// 计算右侧占用数
List<Point> rightPoints = calculateUsedPoints(points, targetIndex + 1, points.size());
//取两个集合中,元素最少的那个集合
movePoints = leftPoints.size() < rightPoints.size() ? leftPoints : rightPoints;
moveScore = (100.0 / (movePoints.size() + 1)) * 0.7;
} else {
movePoints = calculateUsedPoints(points, 0, targetIndex);
moveScore = (100.0 / (movePoints.size() + 1)) * 0.7;
}
//库位得分 = 距离得分 + 移动得分
double totalScore = distanceScore + moveScore;
log.info("【{}】库位距离评分: {}-移位评分: {}-总得分: {}-移位次数: {}",
currPoint.getPointCode(), distanceScore, moveScore, totalScore, movePoints.size());
return new InventoryScore(inventory, totalScore, bestPoint, movePoints);
}
/**
* 使
*
* @param points
* @param start
* @param end
* @return List<Point>
*/
private List<Point> calculateUsedPoints(List<Point> points, int start, int end) {
List<Point> usedPoints = new ArrayList<>();
for (int i = start; i < end && i < points.size(); i++) {
Point point = points.get(i);
if (point != null && point.getStatus().equals(CommonStatusEnum.USED.getValue())) {
usedPoints.add(point);
}
}
return usedPoints;
}
/**
* @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) {
Set<String> errorMsgSet = new LinkedHashSet<>();
// -------------------------- 1. 查询关联数据批量查询减少DB交互--------------------------
Map<Long, Pick> pickMap = queryByPickIdsToMap(pickIds);
Map<Long, PickDetail> pickDetailMap = queryPickDetailByPickIds(pickIds);
List<Task> tasks = taskMapper.queryByPickIds(pickIds);
List<Long> inventoryIds = tasks.stream().map(Task::getInventoryId).distinct().toList();
Map<Long, Inventory> inventoryMap = inventoryService.queryByInventoryIdsToMap(inventoryIds);
// -------------------------- 2. 创建更新列表---------------------------
List<Inventory> updateToInventory = new ArrayList<>();
List<PickDetail> updateToPickDetail = new ArrayList<>();
List<Task> deleteToTask = new ArrayList<>();
// -------------------------- 3. 循环处理任务---------------------------
for (Task task : tasks) {
Pick pick = pickMap.get(task.getPickId());
PickDetail pickDetail = pickDetailMap.get(task.getPickDetailId());
if (task.getAgvTaskId() != null) {
errorMsgSet.add(String.format("取消失败:【%s】已生成AGV任务", pick.getNo()));
continue;
}
//取消数量
BigDecimal cancelQty = task.getPlanQty();
Inventory inventory = inventoryMap.get(task.getInventoryId());
inventory.setQueuedQty(BigDecimalUtil.subtract(inventory.getQueuedQty(), cancelQty, 0));
Integer inv_status = inventory.getQueuedQty().compareTo(BigDecimal.ZERO) > 0 ? InventoryStatusEnum.ALLOCATED.getValue() : InventoryStatusEnum.AVAILABLE.getValue();
inventory.setStatus(inv_status);
updateToInventory.add(inventory);
pickDetail.setAllocatedQty(BigDecimalUtil.subtract(pickDetail.getAllocatedQty(), cancelQty, 0));
Integer pd_status = pickDetail.getStatus();
if (pickDetail.getAllocatedQty().compareTo(BigDecimal.ZERO) <= 0) {
pd_status = PickStatusEnum.CREATED.getValue();
} else if (pickDetail.getAllocatedQty().compareTo(pickDetail.getOrderQty()) < 0) {
pd_status = PickStatusEnum.PARTIAL.getValue();
}
pickDetail.setStatus(pd_status);
updateToPickDetail.add(pickDetail);
deleteToTask.add(task);
iInventoryLogService.addUnAllocInventoryLog(inventory, cancelQty, pick.getOrderNo(), pickDetail.getId(), pickDetail.getDescription());
}
// -------------------------- 4. 批量更新减少DB交互--------------------------
if (CollectionUtils.isNotEmpty(updateToInventory)) {
batchUtils.updateBatchInventory(updateToInventory);
}
if (CollectionUtils.isNotEmpty(updateToPickDetail)) {
batchUtils.updateBatchPickDetail(updateToPickDetail);
}
if (CollectionUtils.isNotEmpty(deleteToTask)) {
taskMapper.deleteByIds(deleteToTask);
}
// -------------------------- 5. 刷新出库单状态 --------------------------
for (Pick pick : pickMap.values()) {
refreshPick(pick, pickDetailMapper.selectByMainId(pick.getId()));
}
return new ArrayList<>(errorMsgSet);
return cancelAllocateProcessor.cancelAllocatePick(pickIds);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void pickTask(List<Task> tasks, Point endPoint) {
if (CollectionUtils.isEmpty(tasks)) {
throw new RuntimeException("无拣货任务");
}
// ================= 1. 数据准备 (批量查询) =================
// 1.1 获取出库单
@ -749,7 +291,7 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
List<Long> inventoryIds = tasks.stream().map(Task::getInventoryId).distinct().toList();
// 1.2 构建映射 Map
Map<Long, Pick> pickMap = queryByPickIdsToMap(pickIds);
Map<Long, Pick> pickMap = pickDetailService.queryByPickIdsToMap(pickIds);
Map<Long, PickDetail> pickDetailMap = queryByPickDetailIdsToMap(pickDetailIds);
Map<Long, Inventory> inventoryMap = inventoryService.queryByInventoryIdsToMap(inventoryIds);
@ -791,7 +333,7 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
deleteToInventoryIds.add(inventory.getId());
}
iInventoryLogService.addPickInventoryLog(inventory, pickedQty, pick.getOrderNo(), task.getPickDetailId(), null);
inventoryLogService.addPickInventoryLog(inventory, pickedQty, pick.getOrderNo(), task.getPickDetailId(), null);
}
// -------------------------- 4. 批量操作 --------------------------
@ -834,7 +376,7 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
// 刷新时重新查询最新的数据
List<PickDetail> latestPickDetails = pickDetailMapper.selectByMainId(pick.getId());
pickDetailsCache.put(pickId, latestPickDetails); // 更新缓存
refreshPick(pick, latestPickDetails);
pickDetailService.refreshPick(pick, latestPickDetails);
}
}
@ -843,12 +385,12 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
* JSON
*/
private String pickTaskCallbackJson(Pick pick, PickDetail pickDetail, Task task, Integer state, String ticket) {
SaiWmsRequest.Task taskReq = new SaiWmsRequest.Task();
SMOMRequest.Task taskReq = new SMOMRequest.Task();
taskReq.setNo(pick.getNo());
taskReq.setOrderNo(pick.getThirdOrderNo());
taskReq.setState(state);
SaiWmsRequest.ShipmentFeedbackDetail shipmentFeedbackDetail = new SaiWmsRequest.ShipmentFeedbackDetail();
SMOMRequest.ShipmentFeedbackDetail shipmentFeedbackDetail = new SMOMRequest.ShipmentFeedbackDetail();
shipmentFeedbackDetail.setLineNo(String.valueOf(pickDetail.getLineNo()));
shipmentFeedbackDetail.setLpn(task.getStockCode());
shipmentFeedbackDetail.setQty(task.getPlanQty().intValue());
@ -869,17 +411,17 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
taskReq.setLastUpdateDate(lastUpdateDate);
SaiWmsRequest.ParameterValue1 parameterValue1 = new SaiWmsRequest.ParameterValue1();
SMOMRequest.ParameterValue1 parameterValue1 = new SMOMRequest.ParameterValue1();
parameterValue1.setValue(List.of(taskReq));
SaiWmsRequest.ParameterValue2 parameterValue2 = new SaiWmsRequest.ParameterValue2();
SMOMRequest.ParameterValue2 parameterValue2 = new SMOMRequest.ParameterValue2();
parameterValue2.setValue(1);
SaiWmsRequest.Context context = new SaiWmsRequest.Context();
SMOMRequest.Context context = new SMOMRequest.Context();
context.setInvOrgId(1);
context.setTicket(ticket);
SaiWmsRequest saiWmsRequest = new SaiWmsRequest();
SMOMRequest saiWmsRequest = new SMOMRequest();
saiWmsRequest.setApiType("SmomWebApiController");
saiWmsRequest.setParameters(List.of(parameterValue1, parameterValue2));
saiWmsRequest.setMethod("OutboundTaskCallbackInterface");
@ -892,7 +434,7 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
public void pickTaskCallback(Pick pick, PickDetail pickDetail, Task task, Integer state) {
// 检查接口开关, 未开启则返回
if (sysDictMapper.queryByDictCode(GeneralConstant.OPEN_FLAG) == null) {
updatePickDetailResponse(pickDetail,task, GeneralConstant.SMOM_FAIL_CODE, "接口未开启");
updatePickDetailResponse(pickDetail, task, GeneralConstant.SMOM_FAIL_CODE, "接口未开启");
return;
}
@ -912,9 +454,9 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
String url = openApiMapper.getRequestUrl(GeneralConstant.OUTBOUND_CALLBACK).getOriginUrl();
JSONObject jsonObject = swmsLoginUtil.sendSMOMResponse(json, url, authorization);
String code = validateResponse(jsonObject);
updatePickDetailResponse(pickDetail,task, code, jsonObject.toJSONString());
updatePickDetailResponse(pickDetail, task, code, jsonObject.toJSONString());
} catch (Exception e) {
updatePickDetailResponse(pickDetail,task, GeneralConstant.SMOM_FAIL_CODE, e.getMessage());
updatePickDetailResponse(pickDetail, task, GeneralConstant.SMOM_FAIL_CODE, e.getMessage());
}
}
@ -944,9 +486,9 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
* @param task
* @param message
*/
private void updatePickDetailResponse(PickDetail pickDetail,Task task, String code, String message) {
private void updatePickDetailResponse(PickDetail pickDetail, Task task, String code, String message) {
if (GeneralConstant.SMOM_SUCCESS_CODE.equals(code)) {
if(PickStatusEnum.PICKED.getValue().equals(pickDetail.getStatus())){
if (PickStatusEnum.PICKED.getValue().equals(pickDetail.getStatus())) {
pickDetail.setStatus(PickStatusEnum.CLOSED.getValue());
pickDetailMapper.updateById(pickDetail);
}

View File

@ -103,7 +103,7 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements IT
}
@Override
public Task bulidTask(String taskNo, Integer taskType, Item item, Point fromPoint, Point toPoint, Stock stock, Long pickId, Long pickDetailId, Long inventoryId, BigDecimal planQty, Integer izAll) {
public Task bulidTask(String taskNo, Integer taskType, Item item, Point fromPoint, Point toPoint, Stock stock, Long pickId, Long pickDetailId,Long itemKeyId, Long inventoryId, BigDecimal planQty, Integer izAll) {
LoginUser sysUser = null;
try {
sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
@ -122,6 +122,7 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements IT
.stockCode(stock.getStockCode())
.pickId(pickId)
.pickDetailId(pickDetailId)
.itemKeyId(itemKeyId)
.inventoryId(inventoryId)
.planQty(planQty)
.moveQty(BigDecimal.ZERO)
@ -223,7 +224,7 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements IT
String taskNo = moveSerialNumberRule.generateSerialNumber(GeneralConstant.MOVE_ORDER_NO);
//根据算法找到最优的目标库位
Point toPoint=null;
Task moveTask = this.bulidTask(taskNo, TaskTypeEnum.MOVE.getValue(), moveItem, fromPoint, toPoint, stock, null, null, inv.getId(), inv.getQuantity(), 0);
Task moveTask = this.bulidTask(taskNo, TaskTypeEnum.MOVE.getValue(), moveItem, fromPoint, toPoint, stock, null, null,inv.getItemKeyId(), inv.getId(), inv.getQuantity(), 0);
moveList.add(moveTask);
log.info("生成移位任务:{}- 容器:{} - 库位:{} - 库存数量:{}", taskNo, stock.getStockCode(), fromPoint.getPointCode(), inv.getQuantity());
inv.setStatus(InventoryStatusEnum.TRANSFER.getValue());
@ -232,6 +233,11 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements IT
return moveList;
}
@Override
public List<Task> queryTaskByMainId(Long id) {
return this.baseMapper.queryTaskByMainId(id);
}
/**
*
*/

View File

@ -0,0 +1,28 @@
package org.cpte.modules.shipping.vo;
import lombok.Data;
import org.cpte.modules.base.entity.Item;
import org.cpte.modules.base.entity.ItemKey;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.inventory.entity.Inventory;
import org.cpte.modules.shipping.entity.Pick;
import org.cpte.modules.shipping.entity.PickDetail;
import java.util.List;
import java.util.Map;
@Data
public class AllocationData {
private Map<Long, Pick> pickMap;
private List<PickDetail> pickDetails;
private Map<Long, Item> itemMap;
private List<Inventory> inventories;
//根据itemKeyId分组
private Map<Long, List<Inventory>> inventoryMap;
private Map<Long, Stock> stockMap;
private Map<Long, Point> pointMap;
//根据物料属性分组
private Map<ItemGroupKey, ItemKey> itemGroupMap;
}

View File

@ -0,0 +1,19 @@
package org.cpte.modules.shipping.vo;
import lombok.Data;
import org.cpte.modules.inventory.entity.Inventory;
import org.cpte.modules.shipping.entity.Pick;
import org.cpte.modules.shipping.entity.PickDetail;
import org.cpte.modules.shipping.entity.Task;
import java.util.List;
import java.util.Map;
@Data
public class CancelAllocateData {
private Map<Long, Pick> pickMap;
private List<PickDetail> pickDetails;
private Map<Long, PickDetail> pickDetailIdMap;
private List<Task> tasks;
private Map<Long, Inventory> inventoryMap;
}

View File

@ -10,7 +10,7 @@ import org.apache.commons.lang3.StringUtils;
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class InventoryGroupKey {
public class ItemGroupKey {
private Long itemId;//物料
private String whCode; // 仓库代码
private String project;//项目号
@ -19,8 +19,8 @@ public class InventoryGroupKey {
private String propC3; // 库存状态
public static InventoryGroupKey of(Long itemId, String whCode, String project, String taskNo, String propC1, String propC3) {
return new InventoryGroupKey(
public static ItemGroupKey of(Long itemId, String whCode, String project, String taskNo, String propC1, String propC3) {
return new ItemGroupKey(
itemId,
StringUtils.defaultIfBlank(whCode, ""),
StringUtils.defaultIfBlank(project, ""),

View File

@ -9,9 +9,8 @@ import org.cpte.modules.constant.GeneralConstant;
import org.cpte.modules.tesAgv.request.CancelTaskRequest;
import org.cpte.modules.tesAgv.request.NewMovePodTaskRequest;
import org.cpte.modules.tesAgv.request.TesCallbackRequest;
import org.cpte.modules.tesAgv.response.TesResult;
import org.cpte.modules.tesAgv.service.ITesAgvService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.api.vo.TesResult;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.config.shiro.IgnoreAuth;
import org.springframework.beans.factory.annotation.Autowired;

View File

@ -49,19 +49,19 @@ public class ITesAgvServiceImpl implements ITesAgvService {
private OpenApiMapper openApiMapper;
@Autowired
private IPointService iPointService;
private IPointService pointService;
@Autowired
private IStockService iStockService;
private IStockService stockService;
@Autowired
private IAsnService iAsnService;
private IAsnService asnService;
@Autowired
private IPickService iPickService;
private IPickService pickService;
@Autowired
private IAgvTaskService iAgvTaskService;
private IAgvTaskService agvTaskService;
@Override
public String generateTesAgvTaskJson(AgvTask agvTask) {
@ -229,23 +229,23 @@ public class ITesAgvServiceImpl implements ITesAgvService {
private void handleEnd(Long asnId, AgvTask agvTask) {
if (BusinessTypeEnum.INBOUND.getValue().equals(agvTask.getType())) {
//收货
iAsnService.receiveGoods(asnId, agvTask.getEndCode());
asnService.receiveAsn(asnId, agvTask.getEndCode());
} else if (BusinessTypeEnum.OUTBOUND.getValue().equals(agvTask.getType())) {
//拣货
Point endPoint = iPointService.validatePoint(agvTask.getEndCode());
Point endPoint = pointService.validatePoint(agvTask.getEndCode());
List<Task> tasks = taskMapper.queryByAgvTask(agvTask.getId());
iPickService.pickTask(tasks, endPoint);
Point startPoint = iPointService.validatePoint(agvTask.getStartCode());
pickService.pickTask(tasks, endPoint);
Point startPoint = pointService.validatePoint(agvTask.getStartCode());
Stock stock = iStockService.validateStock(agvTask.getCarrierCode());
iPointService.unbindPoint(startPoint);
iStockService.bindStock(stock, endPoint);
Stock stock = stockService.validateStock(agvTask.getCarrierCode());
pointService.unbindPoint(startPoint);
stockService.bindStock(stock, endPoint);
//判断AGV任务是否整托整托生成AGV搬运任务
if (agvTask.getIzAll() == 0) {
//查询电梯口点位作为目标点位
String endCode = iPointService.getElevatorPoint(agvTask.getEndCode(), GeneralConstant.CK_ELEVATOR_TASK_INDEX);
iAgvTaskService.createAgvTask(null, agvTask.getCarrierCode(), agvTask.getEndCode(), endCode, null, BusinessTypeEnum.OUTBOUND.getValue(), 0,AgvVendorEnum.HIK.getValue());
String endCode = pointService.getElevatorPoint(agvTask.getEndCode(), GeneralConstant.CK_ELEVATOR_TASK_INDEX);
agvTaskService.createAgvTask(null, agvTask.getCarrierCode(), agvTask.getEndCode(), endCode, null, BusinessTypeEnum.OUTBOUND.getValue(), 0, AgvVendorEnum.HIK.getValue());
}
}
@ -271,11 +271,10 @@ public class ITesAgvServiceImpl implements ITesAgvService {
* @param agvTask
*/
private void handleResend(AgvTask agvTask) {
Long count = agvTaskMapper.existsByStockCode(agvTask.getCarrierCode(), AgvVendorEnum.TES.getValue());
if (count > 0) {
if (agvTaskMapper.existsByStockCode(agvTask.getCarrierCode(), AgvVendorEnum.TES.getValue()) != null) {
throw new RuntimeException("任务已重新生成,请勿重复操作! ");
}
AgvTask newAgvTask = iAgvTaskService.createAgvTask(agvTask.getBusinessDetailId(), agvTask.getCarrierCode(), agvTask.getStartCode(), agvTask.getEndCode(), null, agvTask.getType(),agvTask.getIzAll(), AgvVendorEnum.TES.getValue());
AgvTask newAgvTask = agvTaskService.createAgvTask(agvTask.getBusinessDetailId(), agvTask.getCarrierCode(), agvTask.getStartCode(), agvTask.getEndCode(), null, agvTask.getType(), agvTask.getIzAll(), AgvVendorEnum.TES.getValue());
switch (agvTask.getType()) {
case "INBOUND":
case "OUTBOUND":

View File

@ -14,6 +14,7 @@ import org.cpte.modules.shipping.entity.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -24,7 +25,7 @@ import java.sql.Timestamp;
import java.util.List;
import java.util.function.BiConsumer;
@Service
@Component
public class BatchUtil {
@Autowired
@ -286,8 +287,8 @@ public class BatchUtil {
*/
@Transactional
public void saveBatchTask(List<Task> tasks) {
String sql = "INSERT INTO data_task (id,task_no,item_id,item_code,from_point_id,from_point_code,to_point_id,to_point_code,stock_id,stock_code,pick_id,pick_detail_id,inventory_id,plan_qty,move_qty,task_type,task_status,iz_all,sys_org_code,tenant_id,create_by,create_time) " +
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
String sql = "INSERT INTO data_task (id,task_no,item_id,item_code,from_point_id,from_point_code,to_point_id,to_point_code,stock_id,stock_code,pick_id,pick_detail_id,item_key_id,inventory_id,plan_qty,move_qty,task_type,task_status,iz_all,sys_org_code,tenant_id,create_by,create_time) " +
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
batchInsert(sql, tasks, (ps, task) -> {
try {
ps.setLong(1, IdWorker.getId());
@ -302,16 +303,17 @@ public class BatchUtil {
ps.setString(10, task.getStockCode());
ps.setLong(11, task.getPickId());
ps.setLong(12, task.getPickDetailId());
ps.setLong(13, task.getInventoryId());
ps.setBigDecimal(14, task.getPlanQty());
ps.setBigDecimal(15, task.getMoveQty());
ps.setInt(16, task.getTaskType());
ps.setInt(17, task.getTaskStatus());
ps.setInt(18, task.getIzAll());
ps.setString(19, task.getSysOrgCode());
ps.setLong(20, task.getTenantId());
ps.setString(21, task.getCreateBy());
ps.setTimestamp(22, new Timestamp(task.getCreateTime().getTime()));
ps.setLong(13, task.getItemKeyId());
ps.setLong(14, task.getInventoryId());
ps.setBigDecimal(15, task.getPlanQty());
ps.setBigDecimal(16, task.getMoveQty());
ps.setInt(17, task.getTaskType());
ps.setInt(18, task.getTaskStatus());
ps.setInt(19, task.getIzAll());
ps.setString(20, task.getSysOrgCode());
ps.setLong(21, task.getTenantId());
ps.setString(22, task.getCreateBy());
ps.setTimestamp(23, new Timestamp(task.getCreateTime().getTime()));
} catch (SQLException e) {
throw new RuntimeException(e);
}

View File

@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.CannotAcquireLockException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -13,7 +14,7 @@ import java.time.format.DateTimeFormatter;
import java.util.List;
@Slf4j
@Service
@Component
public class CodeGeneratorUtil {
@Autowired
@ -31,13 +32,8 @@ public class CodeGeneratorUtil {
public String generateSerialNumber(String type) {
try {
String dateStr = LocalDate.now().format(DATE_FORMATTER);
// 使用 SELECT for update 加锁查询并更新
String lockAndUpdateSql =
"SELECT current_seq FROM generator_sequence WHERE type = ? AND date_str = ? for update";
List<Integer> result = jdbcTemplate.queryForList(lockAndUpdateSql, Integer.class, type, dateStr);
String sql = "SELECT current_seq FROM generator_sequence WHERE type = ? AND date_str = ? for update";
List<Integer> result = jdbcTemplate.queryForList(sql, Integer.class, type, dateStr);
if (result.isEmpty()) {
// 插入初始值
String insertSql = "INSERT INTO generator_sequence (type, date_str, current_seq) VALUES (?, ?, 1)";
@ -53,7 +49,7 @@ public class CodeGeneratorUtil {
return type + dateStr + seqStr;
}
} catch (CannotAcquireLockException e) {
throw new RuntimeException("系统繁忙,请稍后重试", e);
throw new RuntimeException("单号生成中,请稍后重试", e);
}
}

View File

@ -0,0 +1,57 @@
package org.cpte.modules.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
public class RedisDistributedLockUtil {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
*
*
* @param lockKey key
* @param expireTime
* @return valuenull
*/
public String tryLock(String lockKey, long expireTime) {
String lockValue = UUID.randomUUID().toString();
// 使用setIfAbsent实现原子性加锁同时设置过期时间防止死锁
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, expireTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success) ? lockValue : null;
}
/**
*
*
* @param lockKey key
* @param lockValue value
* @return
*/
public boolean unlock(String lockKey, String lockValue) {
// 使用Lua脚本保证原子性只有锁的value匹配时才删除
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
RedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue);
return result != null && result == 1;
}
}

View File

@ -6,18 +6,18 @@ import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.cpte.modules.constant.GeneralConstant;
import org.cpte.modules.saiWms.request.SaiWmsRequest;
import org.cpte.modules.saiWms.request.SMOMRequest;
import org.jeecg.modules.openapi.entity.OpenApi;
import org.jeecg.modules.openapi.mapper.OpenApiMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
@Component
public class SwmsLoginUtil {
@Autowired
@ -27,15 +27,15 @@ public class SwmsLoginUtil {
String userName = "LM";
String password = "654321";
SaiWmsRequest.ParameterValue3 parameterValue1 = new SaiWmsRequest.ParameterValue3();
SMOMRequest.ParameterValue3 parameterValue1 = new SMOMRequest.ParameterValue3();
parameterValue1.setValue(userName);
SaiWmsRequest.ParameterValue3 parameterValue2 = new SaiWmsRequest.ParameterValue3();
SMOMRequest.ParameterValue3 parameterValue2 = new SMOMRequest.ParameterValue3();
parameterValue2.setValue(password);
SaiWmsRequest.Context context = new SaiWmsRequest.Context();
SMOMRequest.Context context = new SMOMRequest.Context();
SaiWmsRequest saiWmsRequest = new SaiWmsRequest();
SMOMRequest saiWmsRequest = new SMOMRequest();
saiWmsRequest.setApiType("AuthenticationController");
saiWmsRequest.setParameters(List.of(parameterValue1, parameterValue2));
saiWmsRequest.setMethod("Login");

View File

@ -15,6 +15,10 @@ public class test {
tesMap.put("ak", "ak-EMCNIpxwfMXzJ8rj");
tesMap.put("sk", "HtT14KlSwCfLfLyGe3FeJVPc3zmjZwXR");
Map<String, String> agvMap = new HashMap<>();
agvMap.put("ak", "ak-EE2BXylYpsRcW9LY");
agvMap.put("sk", "20xCnGWwgh6reNqmp5CbW7WjVBHVC9nN");
long timestamp = System.currentTimeMillis();
System.out.println("sai-timestamp:" + timestamp);
@ -22,7 +26,9 @@ public class test {
System.out.println("=======================================================================================");
System.out.println("tes-timestamp:" + timestamp);
System.out.println("tes-signature:" + md5(tesMap.get("ak")+ tesMap.get("sk") + timestamp));
System.out.println("=======================================================================================");
System.out.println("agv-timestamp:" + timestamp);
System.out.println("agv-signature:" + md5(agvMap.get("ak")+ agvMap.get("sk") + timestamp));
}
public static String md5(String sourceStr) {

View File

@ -7,6 +7,9 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import org.apache.poi.ss.formula.functions.T;
import org.jeecg.common.api.vo.HikResult;
import org.jeecg.common.api.vo.TesResult;
import org.jeecg.modules.openapi.swagger.*;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
@ -35,6 +38,7 @@ import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import jakarta.servlet.http.HttpServletRequest;
import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;
@ -121,11 +125,11 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
*/
@DeleteMapping(value = "/deleteBatch")
public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
List<String> list= Arrays.asList(ids.split(","));
list.forEach(id->{
OpenApi openApi = service.getById(id);
redisUtil.del(openApi.getRequestUrl());
});
List<String> list = Arrays.asList(ids.split(","));
list.forEach(id -> {
OpenApi openApi = service.getById(id);
redisUtil.del(openApi.getRequestUrl());
});
this.service.removeByIds(Arrays.asList(ids.split(",")));
return Result.ok("批量删除成功!");
}
@ -144,11 +148,12 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
/**
*
*
* @param path
* @return
*/
@RequestMapping(value = "/call/{path}", method = {RequestMethod.GET,RequestMethod.POST})
public Result<?> call(@PathVariable String path, @RequestBody(required = false) String json, HttpServletRequest request) {
@RequestMapping(value = "/call/{path}", method = {RequestMethod.GET, RequestMethod.POST})
public Object call(@PathVariable String path, @RequestBody(required = false) String json, HttpServletRequest request) {
OpenApi openApi = service.findByPath(path);
if (Objects.isNull(openApi)) {
Map<String, Object> result = new HashMap<>();
@ -158,8 +163,8 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
}
HttpHeaders httpHeaders = new HttpHeaders();
if (StrUtil.isNotEmpty(openApi.getHeadersJson())) {
List<OpenApiHeader> headers = JSON.parseArray(openApi.getHeadersJson(),OpenApiHeader.class);
if (headers.size()>0) {
List<OpenApiHeader> headers = JSON.parseArray(openApi.getHeadersJson(), OpenApiHeader.class);
if (headers.size() > 0) {
for (OpenApiHeader header : headers) {
httpHeaders.put(header.getHeaderKey(), Lists.newArrayList(request.getHeader(header.getHeaderKey())));
}
@ -173,9 +178,9 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
SysUser systemUser = sysUserService.getUserByName(openApiAuth.getCreateBy());
String token = this.getToken(systemUser.getUsername(), systemUser.getPassword());
httpHeaders.put("X-Access-Token", Lists.newArrayList(token));
httpHeaders.put("Content-Type",Lists.newArrayList("application/json"));
httpHeaders.put("Content-Type", Lists.newArrayList("application/json"));
HttpEntity<String> httpEntity = new HttpEntity<>(json, httpHeaders);
url = RestUtil.getBaseUrl() + url;
url = RestUtil.getBaseUrl() + url;
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
if (HttpMethod.GET.matches(method)
|| HttpMethod.DELETE.matches(method)
@ -184,16 +189,16 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
//拼接参数
if (!request.getParameterMap().isEmpty()) {
if (StrUtil.isNotEmpty(openApi.getParamsJson())) {
List<OpenApiParam> params = JSON.parseArray(openApi.getParamsJson(),OpenApiParam.class);
if (params.size()>0) {
List<OpenApiParam> params = JSON.parseArray(openApi.getParamsJson(), OpenApiParam.class);
if (params.size() > 0) {
Map<String, OpenApiParam> openApiParamMap = params.stream().collect(Collectors.toMap(p -> p.getParamKey(), p -> p, (e, r) -> e));
request.getParameterMap().forEach((k, v) -> {
OpenApiParam openApiParam = openApiParamMap.get(k);
if (Objects.nonNull(openApiParam)) {
if(v==null&&StrUtil.isNotEmpty(openApiParam.getDefaultValue())){
if (v == null && StrUtil.isNotEmpty(openApiParam.getDefaultValue())) {
builder.queryParam(openApiParam.getParamKey(), openApiParam.getDefaultValue());
}
if (v!=null){
if (v != null) {
builder.queryParam(openApiParam.getParamKey(), v);
}
}
@ -204,7 +209,17 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
}
}
URI targetUrl = builder.build().encode().toUri();
return restTemplate.exchange(targetUrl.toString(), Objects.requireNonNull(HttpMethod.valueOf(method)), httpEntity, Result.class, request.getParameterMap()).getBody();
Class<?> responseType;
if (openApi.getRequestUrl().equals("wCB0lcSa")) {
//AGV
responseType = HikResult.class;
} else if (openApi.getRequestUrl().equals("OS6YMEqg")) {
//TES
responseType = TesResult.class;
} else {
responseType = Result.class;
}
return restTemplate.exchange(targetUrl.toString(), Objects.requireNonNull(HttpMethod.valueOf(method)), httpEntity, responseType, request.getParameterMap()).getBody();
}
/**
@ -251,7 +266,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
operation.setTags(Lists.newArrayList("openapi"));
operation.setSummary(openApi.getName());
operation.setDescription(openApi.getName());
operation.setOperationId(openApi.getRequestUrl()+"Using"+openApi.getRequestMethod());
operation.setOperationId(openApi.getRequestUrl() + "Using" + openApi.getRequestMethod());
operation.setProduces(Lists.newArrayList("application/json"));
parameters(operation, openApi);
@ -261,19 +276,19 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
definition.setType("object");
Map<String, SwaggerDefinitionProperties> definitionProperties = new HashMap<>();
definition.setProperties(definitionProperties);
if (openApi.getBody()!=null){
if (openApi.getBody() != null) {
JSONObject jsonObject = JSONObject.parseObject(openApi.getBody());
if (jsonObject.size()>0){
if (jsonObject.size() > 0) {
for (Map.Entry<String, Object> properties : jsonObject.entrySet()) {
SwaggerDefinitionProperties swaggerDefinitionProperties = new SwaggerDefinitionProperties();
swaggerDefinitionProperties.setType("string");
swaggerDefinitionProperties.setDescription(properties.getValue()+"");
swaggerDefinitionProperties.setDescription(properties.getValue() + "");
definitionProperties.put(properties.getKey(), swaggerDefinitionProperties);
}
}
}
// body的definition构建完成
definitions.put(openApi.getRequestUrl()+"Using"+openApi.getRequestMethod()+"body", definition);
definitions.put(openApi.getRequestUrl() + "Using" + openApi.getRequestMethod() + "body", definition);
SwaggerOperationParameter bodyParameter = new SwaggerOperationParameter();
bodyParameter.setDescription(openApi.getName() + " body");
@ -282,7 +297,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
bodyParameter.setRequired(true);
Map<String, String> bodySchema = new HashMap<>();
bodySchema.put("$ref", "#/definitions/" + openApi.getRequestUrl()+"Using"+openApi.getRequestMethod()+"body");
bodySchema.put("$ref", "#/definitions/" + openApi.getRequestUrl() + "Using" + openApi.getRequestMethod() + "body");
bodyParameter.setSchema(bodySchema);
// 构建参数构建完成
@ -351,7 +366,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
operation.setResponses(responses);
operations.put(openApi.getRequestMethod().toLowerCase(), operation);
paths.put("/openapi/call/"+openApi.getRequestUrl(), operations);
paths.put("/openapi/call/" + openApi.getRequestUrl(), operations);
}
swaggerModel.setDefinitions(definitions);
@ -361,7 +376,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
private void parameters(SwaggerOperation operation, OpenApi openApi) {
List<SwaggerOperationParameter> parameters = new ArrayList<>();
if (openApi.getParamsJson()!=null) {
if (openApi.getParamsJson() != null) {
List<OpenApiParam> openApiParams = JSON.parseArray(openApi.getParamsJson(), OpenApiParam.class);
for (OpenApiParam openApiParam : openApiParams) {
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
@ -372,7 +387,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
parameters.add(parameter);
}
}
if (openApi.getHeadersJson()!=null) {
if (openApi.getHeadersJson() != null) {
List<OpenApiHeader> openApiHeaders = JSON.parseArray(openApi.getHeadersJson(), OpenApiHeader.class);
for (OpenApiHeader openApiHeader : openApiHeaders) {
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
@ -410,6 +425,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
/**
*
*
* @return
*/
@GetMapping("genPath")