no message
parent
74564b64ab
commit
981b83aa2d
|
|
@ -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 2019年1月19日
|
||||
*/
|
||||
@JsonPropertyOrder({
|
||||
"code",
|
||||
"message",
|
||||
"data"
|
||||
})
|
||||
@Data
|
||||
public class HikResult implements Serializable {
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
@ -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 然后在请求list的时候将这个字典text,已字段名称加_dictText形式返回到前端
|
||||
* 例输入当前返回值的就会多出一个sex_dictText字段
|
||||
* {
|
||||
* sex:1,
|
||||
* sex_dictText:"男"
|
||||
* sex:1,
|
||||
* sex_dictText:"男"
|
||||
* }
|
||||
* 前端直接取值sext_dictText在table里面无需再进行前端的字典转换了
|
||||
* customRender:function (text) {
|
||||
* if(text==1){
|
||||
* return "男";
|
||||
* }else if(text==2){
|
||||
* return "女";
|
||||
* }else{
|
||||
* return text;
|
||||
* }
|
||||
* }
|
||||
* 目前vue是这么进行字典渲染到table上的多了就很麻烦了 这个直接在服务端渲染完成前端可以直接用
|
||||
* customRender:function (text) {
|
||||
* if(text==1){
|
||||
* return "男";
|
||||
* }else if(text==2){
|
||||
* return "女";
|
||||
* }else{
|
||||
* return text;
|
||||
* }
|
||||
* }
|
||||
* 目前vue是这么进行字典渲染到table上的多了就很麻烦了 这个直接在服务端渲染完成前端可以直接用
|
||||
*
|
||||
* @param result
|
||||
*/
|
||||
private Object parseDictText(Object result) {
|
||||
|
|
@ -117,24 +105,24 @@ public class DictAspect {
|
|||
// 字典数据列表, key = 字典code,value=数据列表
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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任务列表
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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.* " +
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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} ")
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/**
|
||||
* 入库任务回传
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 容器同步
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ public class OutboundRequest {
|
|||
// 数量
|
||||
@NotNull(message = "数量不能为空")
|
||||
@JsonProperty("Qty")
|
||||
private BigDecimal qty;
|
||||
private Double qty;
|
||||
|
||||
// 项目号
|
||||
@JsonProperty("Project")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import lombok.Data;
|
|||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class SaiWmsRequest {
|
||||
public class SMOMRequest {
|
||||
@JsonProperty("ApiType")
|
||||
private String apiType;
|
||||
|
||||
|
|
@ -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 {
|
||||
|
||||
/**
|
||||
* 入库任务下发
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
/**
|
||||
* 根据出库单ID查询出库单Map
|
||||
*
|
||||
* @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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
||||
/**
|
||||
* 取消分配
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/**
|
||||
* 根据Task集合生成AGV出库任务
|
||||
|
|
@ -65,4 +65,5 @@ public interface ITaskService extends IService<Task> {
|
|||
*/
|
||||
List<Task> bulidMoveTask(List<Long> movePointIds);
|
||||
|
||||
List<Task> queryTaskByMainId(Long id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择最优移位目标
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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, ""),
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 锁的value(用于释放锁时验证),如果获取失败返回null
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Reference in New Issue