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.JsonProperty;
|
||||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
@ -14,11 +12,6 @@ import java.io.Serializable;
|
||||||
* @email cpteos@163.com
|
* @email cpteos@163.com
|
||||||
* @date 2019年1月19日
|
* @date 2019年1月19日
|
||||||
*/
|
*/
|
||||||
@JsonPropertyOrder({
|
|
||||||
"code",
|
|
||||||
"message",
|
|
||||||
"data"
|
|
||||||
})
|
|
||||||
@Data
|
@Data
|
||||||
public class HikResult implements Serializable {
|
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 com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
@ -49,38 +49,25 @@ public class DictAspect {
|
||||||
|
|
||||||
private static final String JAVA_UTIL_DATE = "java.util.Date";
|
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
|
||||||
*/
|
*/
|
||||||
@Pointcut("(@within(org.springframework.web.bind.annotation.RestController) || " +
|
@Pointcut("(@within(org.springframework.web.bind.annotation.RestController) || " +
|
||||||
"@within(org.springframework.stereotype.Controller) || " +
|
"@within(org.springframework.stereotype.Controller) || @annotation(org.jeecg.common.aspect.annotation.AutoDict)) " +
|
||||||
"@annotation(org.jeecg.common.aspect.annotation.AutoDict)) " +
|
"&& execution(public org.jeecg.common.api.vo.Result org.jeecg..*.*(..))")
|
||||||
"&& (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.*.*(..))))")
|
|
||||||
public void excudeService() {
|
public void excudeService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Around("excudeService()")
|
@Around("excudeService()")
|
||||||
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
|
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
|
||||||
long time1=System.currentTimeMillis();
|
long time1 = System.currentTimeMillis();
|
||||||
Object result = pjp.proceed();
|
Object result = pjp.proceed();
|
||||||
long time2=System.currentTimeMillis();
|
long time2 = System.currentTimeMillis();
|
||||||
log.debug("获取JSON数据 耗时:"+(time2-time1)+"ms");
|
log.debug("获取JSON数据 耗时:" + (time2 - time1) + "ms");
|
||||||
long start=System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
result=this.parseDictText(result);
|
result = this.parseDictText(result);
|
||||||
long end=System.currentTimeMillis();
|
long end = System.currentTimeMillis();
|
||||||
log.debug("注入字典到JSON数据 耗时"+(end-start)+"ms");
|
log.debug("注入字典到JSON数据 耗时" + (end - start) + "ms");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,20 +77,21 @@ public class DictAspect {
|
||||||
* 示例为SysUser 字段为sex 添加了注解@Dict(dicCode = "sex") 会在字典服务立马查出来对应的text 然后在请求list的时候将这个字典text,已字段名称加_dictText形式返回到前端
|
* 示例为SysUser 字段为sex 添加了注解@Dict(dicCode = "sex") 会在字典服务立马查出来对应的text 然后在请求list的时候将这个字典text,已字段名称加_dictText形式返回到前端
|
||||||
* 例输入当前返回值的就会多出一个sex_dictText字段
|
* 例输入当前返回值的就会多出一个sex_dictText字段
|
||||||
* {
|
* {
|
||||||
* sex:1,
|
* sex:1,
|
||||||
* sex_dictText:"男"
|
* sex_dictText:"男"
|
||||||
* }
|
* }
|
||||||
* 前端直接取值sext_dictText在table里面无需再进行前端的字典转换了
|
* 前端直接取值sext_dictText在table里面无需再进行前端的字典转换了
|
||||||
* customRender:function (text) {
|
* customRender:function (text) {
|
||||||
* if(text==1){
|
* if(text==1){
|
||||||
* return "男";
|
* return "男";
|
||||||
* }else if(text==2){
|
* }else if(text==2){
|
||||||
* return "女";
|
* return "女";
|
||||||
* }else{
|
* }else{
|
||||||
* return text;
|
* return text;
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* 目前vue是这么进行字典渲染到table上的多了就很麻烦了 这个直接在服务端渲染完成前端可以直接用
|
* 目前vue是这么进行字典渲染到table上的多了就很麻烦了 这个直接在服务端渲染完成前端可以直接用
|
||||||
|
*
|
||||||
* @param result
|
* @param result
|
||||||
*/
|
*/
|
||||||
private Object parseDictText(Object result) {
|
private Object parseDictText(Object result) {
|
||||||
|
|
@ -117,24 +105,24 @@ public class DictAspect {
|
||||||
// 字典数据列表, key = 字典code,value=数据列表
|
// 字典数据列表, key = 字典code,value=数据列表
|
||||||
Map<String, List<String>> dataListMap = new HashMap<>(5);
|
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】 判断是否含有字典注解,没有注解返回-----
|
//update-begin--Author:zyf -- Date:20220606 ----for:【VUEN-1230】 判断是否含有字典注解,没有注解返回-----
|
||||||
Boolean hasDict= checkHasDict(records);
|
Boolean hasDict = checkHasDict(records);
|
||||||
if(!hasDict){
|
if (!hasDict) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug(" __ 进入字典翻译切面 DictAspect —— " );
|
log.debug(" __ 进入字典翻译切面 DictAspect —— ");
|
||||||
//update-end--Author:zyf -- Date:20220606 ----for:【VUEN-1230】 判断是否含有字典注解,没有注解返回-----
|
//update-end--Author:zyf -- Date:20220606 ----for:【VUEN-1230】 判断是否含有字典注解,没有注解返回-----
|
||||||
for (Object record : records) {
|
for (Object record : records) {
|
||||||
String json="{}";
|
String json = "{}";
|
||||||
try {
|
try {
|
||||||
//update-begin--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
|
//update-begin--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
|
||||||
//解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat
|
//解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat
|
||||||
json = objectMapper.writeValueAsString(record);
|
json = objectMapper.writeValueAsString(record);
|
||||||
//update-end--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
|
//update-end--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
|
||||||
} catch (JsonProcessingException e) {
|
} 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顺序错乱 -----
|
//update-begin--Author:scott -- Date:20211223 ----for:【issues/3303】restcontroller返回json数据后key顺序错乱 -----
|
||||||
JSONObject item = JSONObject.parseObject(json, Feature.OrderedField);
|
JSONObject item = JSONObject.parseObject(json, Feature.OrderedField);
|
||||||
|
|
@ -148,7 +136,7 @@ public class DictAspect {
|
||||||
if (oConvertUtils.isEmpty(value)) {
|
if (oConvertUtils.isEmpty(value)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
//update-end--Author:scott -- Date:20190603 ----for:解决继承实体字段无法翻译问题------
|
//update-end--Author:scott -- Date:20190603 ----for:解决继承实体字段无法翻译问题------
|
||||||
if (field.getAnnotation(Dict.class) != null) {
|
if (field.getAnnotation(Dict.class) != null) {
|
||||||
if (!dictFieldList.contains(field)) {
|
if (!dictFieldList.contains(field)) {
|
||||||
dictFieldList.add(field);
|
dictFieldList.add(field);
|
||||||
|
|
@ -172,8 +160,8 @@ public class DictAspect {
|
||||||
//date类型默认转换string格式化日期
|
//date类型默认转换string格式化日期
|
||||||
//update-begin--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
|
//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){
|
//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");
|
//SimpleDateFormat aDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
// item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
|
// item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
|
||||||
//}
|
//}
|
||||||
//update-end--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
|
//update-end--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错-----
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +191,7 @@ public class DictAspect {
|
||||||
String value = record.getString(field.getName());
|
String value = record.getString(field.getName());
|
||||||
if (oConvertUtils.isNotEmpty(value)) {
|
if (oConvertUtils.isNotEmpty(value)) {
|
||||||
List<DictModel> dictModels = translText.get(fieldDictCode);
|
List<DictModel> dictModels = translText.get(fieldDictCode);
|
||||||
if(dictModels==null || dictModels.size()==0){
|
if (dictModels == null || dictModels.size() == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -242,6 +230,7 @@ public class DictAspect {
|
||||||
* 一次性把所有的字典都翻译了
|
* 一次性把所有的字典都翻译了
|
||||||
* 1. 所有的普通数据字典的所有数据只执行一次SQL
|
* 1. 所有的普通数据字典的所有数据只执行一次SQL
|
||||||
* 2. 表字典相同的所有数据只执行一次SQL
|
* 2. 表字典相同的所有数据只执行一次SQL
|
||||||
|
*
|
||||||
* @param dataListMap
|
* @param dataListMap
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
|
@ -311,7 +300,7 @@ public class DictAspect {
|
||||||
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
|
||||||
//update-begin---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
|
//update-begin---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
|
||||||
if(null == dataSource){
|
if (null == dataSource) {
|
||||||
dataSource = "";
|
dataSource = "";
|
||||||
}
|
}
|
||||||
//update-end---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
|
//update-end---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
|
||||||
|
|
@ -393,7 +382,8 @@ public class DictAspect {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 翻译字典文本
|
* 翻译字典文本
|
||||||
|
*
|
||||||
* @param code
|
* @param code
|
||||||
* @param text
|
* @param text
|
||||||
* @param table
|
* @param table
|
||||||
|
|
@ -402,39 +392,39 @@ public class DictAspect {
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
private String translateDictValue(String code, String text, String table, String key) {
|
private String translateDictValue(String code, String text, String table, String key) {
|
||||||
if(oConvertUtils.isEmpty(key)) {
|
if (oConvertUtils.isEmpty(key)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
StringBuffer textValue=new StringBuffer();
|
StringBuffer textValue = new StringBuffer();
|
||||||
String[] keys = key.split(",");
|
String[] keys = key.split(",");
|
||||||
for (String k : keys) {
|
for (String k : keys) {
|
||||||
String tmpValue = null;
|
String tmpValue = null;
|
||||||
log.debug(" 字典 key : "+ k);
|
log.debug(" 字典 key : " + k);
|
||||||
if (k.trim().length() == 0) {
|
if (k.trim().length() == 0) {
|
||||||
continue; //跳过循环
|
continue; //跳过循环
|
||||||
}
|
}
|
||||||
//update-begin--Author:scott -- Date:20210531 ----for: !56 优化微服务应用下存在表字段需要字典翻译时加载缓慢问题-----
|
//update-begin--Author:scott -- Date:20210531 ----for: !56 优化微服务应用下存在表字段需要字典翻译时加载缓慢问题-----
|
||||||
if (!StringUtils.isEmpty(table)){
|
if (!StringUtils.isEmpty(table)) {
|
||||||
log.debug("--DictAspect------dicTable="+ table+" ,dicText= "+text+" ,dicCode="+code);
|
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());
|
String keyString = String.format("sys:cache:dictTable::SimpleKey [%s,%s,%s,%s]", table, text, code, k.trim());
|
||||||
if (redisTemplate.hasKey(keyString)){
|
if (redisTemplate.hasKey(keyString)) {
|
||||||
try {
|
try {
|
||||||
tmpValue = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
|
tmpValue = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn(e.getMessage());
|
log.warn(e.getMessage());
|
||||||
}
|
}
|
||||||
}else {
|
} else {
|
||||||
tmpValue= commonApi.translateDictFromTable(table,text,code,k.trim());
|
tmpValue = commonApi.translateDictFromTable(table, text, code, k.trim());
|
||||||
}
|
}
|
||||||
}else {
|
} else {
|
||||||
String keyString = String.format("sys:cache:dict::%s:%s",code,k.trim());
|
String keyString = String.format("sys:cache:dict::%s:%s", code, k.trim());
|
||||||
if (redisTemplate.hasKey(keyString)){
|
if (redisTemplate.hasKey(keyString)) {
|
||||||
try {
|
try {
|
||||||
tmpValue = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
|
tmpValue = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn(e.getMessage());
|
log.warn(e.getMessage());
|
||||||
}
|
}
|
||||||
}else {
|
} else {
|
||||||
tmpValue = commonApi.translateDict(code, k.trim());
|
tmpValue = commonApi.translateDict(code, k.trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -453,11 +443,12 @@ public class DictAspect {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检测返回结果集中是否包含Dict注解
|
* 检测返回结果集中是否包含Dict注解
|
||||||
|
*
|
||||||
* @param records
|
* @param records
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private Boolean checkHasDict(List<Object> records){
|
private Boolean checkHasDict(List<Object> records) {
|
||||||
if(oConvertUtils.isNotEmpty(records) && records.size()>0){
|
if (oConvertUtils.isNotEmpty(records) && records.size() > 0) {
|
||||||
for (Field field : oConvertUtils.getAllFields(records.get(0))) {
|
for (Field field : oConvertUtils.getAllFields(records.get(0))) {
|
||||||
if (oConvertUtils.isNotEmpty(field.getAnnotation(Dict.class))) {
|
if (oConvertUtils.isNotEmpty(field.getAnnotation(Dict.class))) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ public interface AgvTaskMapper extends BaseMapper<AgvTask> {
|
||||||
* @param carrierCode 容器
|
* @param carrierCode 容器
|
||||||
* @param agvVendor 供应商;AGV/TES
|
* @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 ")
|
@Select(value = "select 1 from data_agv_task where carrier_code = #{carrierCode} and status in (1,2,3) and agv_vendor = #{agvVendor} LIMIT 1 ")
|
||||||
Long existsByStockCode(@Param("carrierCode") String carrierCode, @Param("agvVendor") String agvVendor);
|
Integer existsByStockCode(@Param("carrierCode") String carrierCode, @Param("agvVendor") String agvVendor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据起点编码验证AGV任务是否存在
|
* 根据起点编码验证AGV任务是否存在
|
||||||
|
|
@ -29,8 +29,8 @@ public interface AgvTaskMapper extends BaseMapper<AgvTask> {
|
||||||
* @param startCode 起点
|
* @param startCode 起点
|
||||||
* @param agvVendor 供应商;AGV/TES
|
* @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 ")
|
@Select(value = "select 1 from data_agv_task where start_code = #{startCode} and status in (2,3) and agv_vendor = #{agvVendor} LIMIT 1 ")
|
||||||
Long existsByStartCode(@Param("startCode") String startCode, @Param("agvVendor") String agvVendor);
|
Integer existsByStartCode(@Param("startCode") String startCode, @Param("agvVendor") String agvVendor);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -39,8 +39,8 @@ public interface AgvTaskMapper extends BaseMapper<AgvTask> {
|
||||||
* @param endCode 终点
|
* @param endCode 终点
|
||||||
* @param agvVendor 供应商;AGV/TES
|
* @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 ")
|
@Select(value = "select 1 from data_agv_task where end_code = #{endCode} and status in (2,3) and agv_vendor = #{agvVendor} LIMIT 1 ")
|
||||||
Long existsByEndCode(@Param("endCode") String endCode, @Param("agvVendor") String agvVendor);
|
Integer existsByEndCode(@Param("endCode") String endCode, @Param("agvVendor") String agvVendor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询AGV任务列表
|
* 查询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.apache.commons.lang3.StringUtils;
|
||||||
import org.cpte.modules.agvTask.entity.AgvTask;
|
import org.cpte.modules.agvTask.entity.AgvTask;
|
||||||
import org.cpte.modules.agvTask.mapper.AgvTaskMapper;
|
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.constant.enums.*;
|
||||||
import org.cpte.modules.conveyorLine.request.ScanTrayRequest;
|
import org.cpte.modules.conveyorLine.request.ScanTrayRequest;
|
||||||
import org.cpte.modules.conveyorLine.service.IConveyorLineService;
|
import org.cpte.modules.conveyorLine.service.IConveyorLineService;
|
||||||
import org.cpte.modules.conveyorLine.vo.PointScore;
|
import org.cpte.modules.conveyorLine.service.ScanTrayProcessor;
|
||||||
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.shipping.entity.Task;
|
import org.cpte.modules.shipping.entity.Task;
|
||||||
import org.cpte.modules.shipping.mapper.TaskMapper;
|
import org.cpte.modules.shipping.mapper.TaskMapper;
|
||||||
|
import org.cpte.modules.utils.RedisDistributedLockUtil;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class IConveyorLineServiceImpl implements IConveyorLineService {
|
public class IConveyorLineServiceImpl implements IConveyorLineService {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PointMapper pointMapper;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private StockMapper stockMapper;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AsnMapper asnMapper;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AsnDetailMapper asnDetailMapper;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private TaskMapper taskMapper;
|
private TaskMapper taskMapper;
|
||||||
|
|
||||||
|
|
@ -58,296 +29,27 @@ public class IConveyorLineServiceImpl implements IConveyorLineService {
|
||||||
private AgvTaskMapper agvTaskMapper;
|
private AgvTaskMapper agvTaskMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ItemKeyMapper itemKeyMapper;
|
private ScanTrayProcessor scanTrayProcessor;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private InventoryMapper inventoryMapper;
|
private RedisDistributedLockUtil redissonLock;
|
||||||
|
|
||||||
@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("工作站不能为空");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public void scanTray(ScanTrayRequest scanTrayRequest) {
|
public void scanTray(ScanTrayRequest scanTrayRequest) {
|
||||||
//验证参数
|
String lockKey = "scanTray:" + scanTrayRequest.getStockCode();
|
||||||
validateParams(scanTrayRequest);
|
String lockValue = null;
|
||||||
|
try {
|
||||||
//工作站
|
lockValue = redissonLock.tryLock(lockKey, 10);
|
||||||
Point station = pointMapper.queryByPointCode(scanTrayRequest.getStation());
|
if (StringUtils.isEmpty(lockValue)) {
|
||||||
|
throw new RuntimeException("扫描处理中,请稍后重试");
|
||||||
//容器
|
}
|
||||||
Stock stock = stockMapper.queryByStockCode(scanTrayRequest.getStockCode());
|
scanTrayProcessor.scanTray(scanTrayRequest);
|
||||||
|
} finally {
|
||||||
//验证入库信息
|
if (StringUtils.isNotEmpty(lockValue)) {
|
||||||
List<AsnDetail> asnDetails = asnDetailMapper.queryByStockCode(stock.getId(), AsnStatusEnum.CREATED.getValue());
|
redissonLock.unlock(lockKey, lockValue);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 限制最高分
|
|
||||||
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
|
@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.SubmitRequest;
|
||||||
import org.cpte.modules.hikAgv.request.TaskReporterRequest;
|
import org.cpte.modules.hikAgv.request.TaskReporterRequest;
|
||||||
import org.cpte.modules.hikAgv.service.IHikAgvService;
|
import org.cpte.modules.hikAgv.service.IHikAgvService;
|
||||||
import org.cpte.modules.tesAgv.response.TesResult;
|
import org.jeecg.common.api.vo.HikResult;
|
||||||
import org.jeecg.common.api.vo.Result;
|
|
||||||
import org.cpte.modules.hikAgv.response.HikResult;
|
|
||||||
import org.jeecg.common.aspect.annotation.AutoLog;
|
import org.jeecg.common.aspect.annotation.AutoLog;
|
||||||
import org.jeecg.config.shiro.IgnoreAuth;
|
import org.jeecg.config.shiro.IgnoreAuth;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
|
||||||
|
|
@ -240,8 +240,7 @@ public class IHikAgvServiceImpl implements IHikAgvService {
|
||||||
* @param agvTask 任务
|
* @param agvTask 任务
|
||||||
*/
|
*/
|
||||||
private void handleResend(AgvTask agvTask) {
|
private void handleResend(AgvTask agvTask) {
|
||||||
Long count = agvTaskMapper.existsByStockCode(agvTask.getCarrierCode(), AgvVendorEnum.HIK.getValue());
|
if (agvTaskMapper.existsByStockCode(agvTask.getCarrierCode(), AgvVendorEnum.HIK.getValue()) !=null) {
|
||||||
if (count > 0) {
|
|
||||||
throw new RuntimeException("任务已重新生成,请勿重复操作! ");
|
throw new RuntimeException("任务已重新生成,请勿重复操作! ");
|
||||||
}
|
}
|
||||||
AgvTask newAgvTask = iAgvTaskService.createAgvTask(agvTask.getBusinessDetailId(), agvTask.getCarrierCode(), agvTask.getStartCode(), agvTask.getEndCode(), null, agvTask.getType(),agvTask.getIzAll(), AgvVendorEnum.HIK.getValue());
|
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 容器
|
* @param stockId 容器
|
||||||
* @return 库存数据
|
* @return 库存数据
|
||||||
*/
|
*/
|
||||||
@Select("select * from data_inventory where stock_id = #{stockId} and quantity>0 for update")
|
@Select("select 1 from data_inventory where stock_id = #{stockId} and quantity>0 LIMIT 1 ")
|
||||||
Inventory queryByStockId(@Param("stockId") Long stockId);
|
Integer exitsStockInventory(@Param("stockId") Long stockId);
|
||||||
|
|
||||||
// 查询相邻库位(同一巷道,深度±3范围内)
|
// 查询相邻库位(同一巷道,深度±3范围内)
|
||||||
@Select("SELECT di.* " +
|
@Select("SELECT di.* " +
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package org.cpte.modules.quartz.job;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.cpte.modules.shipping.mapper.PickMapper;
|
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.IPickService;
|
||||||
import org.cpte.modules.shipping.service.ITaskService;
|
import org.cpte.modules.shipping.service.ITaskService;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
|
@ -28,10 +27,7 @@ public class AllocateJob implements Job {
|
||||||
private PickMapper pickMapper;
|
private PickMapper pickMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private TaskMapper taskMapper;
|
private IPickService pickService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IPickService iPickService;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ITaskService iTaskService;
|
private ITaskService iTaskService;
|
||||||
|
|
@ -52,7 +48,7 @@ public class AllocateJob implements Job {
|
||||||
if (CollectionUtils.isNotEmpty(pickList)) {
|
if (CollectionUtils.isNotEmpty(pickList)) {
|
||||||
// 分配出库
|
// 分配出库
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
List<String> resultMsg = iPickService.allocatePick(pickList);
|
List<String> resultMsg = pickService.allocatePick(pickList);
|
||||||
long endTime = System.currentTimeMillis();
|
long endTime = System.currentTimeMillis();
|
||||||
log.info("分配出库明细耗时:{}ms", endTime - startTime);
|
log.info("分配出库明细耗时:{}ms", endTime - startTime);
|
||||||
if (CollectionUtils.isNotEmpty(resultMsg)) {
|
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";
|
String taskSubmitUrl = "http://localhost:8000/cpte-wms/rcs/rtas/api/robot/controller/task/submit";
|
||||||
for (AgvTask agvTask : agvTaskList) {
|
for (AgvTask agvTask : agvTaskList) {
|
||||||
if(BusinessTypeEnum.INBOUND.getValue().equals(agvTask.getType())){
|
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) {
|
if (isStartCodeAvailable) {
|
||||||
hikAgvService.sendHikAgvTask(
|
hikAgvService.sendHikAgvTask(
|
||||||
taskSubmitUrl,
|
taskSubmitUrl,
|
||||||
|
|
@ -41,7 +41,7 @@ public class HikAgvJob implements Job {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}else if(BusinessTypeEnum.OUTBOUND.getValue().equals(agvTask.getType())){
|
}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) {
|
if (isEndCodeAvailable) {
|
||||||
hikAgvService.sendHikAgvTask(
|
hikAgvService.sendHikAgvTask(
|
||||||
taskSubmitUrl,
|
taskSubmitUrl,
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ public interface AsnDetailMapper extends BaseMapper<AsnDetail> {
|
||||||
* @param status 状态
|
* @param status 状态
|
||||||
* @return AsnDetail
|
* @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);
|
List<AsnDetail> queryByStockCode(@Param("stockId") Long stockId, @Param("status") Integer status);
|
||||||
|
|
||||||
@Select("select MAX(line_no) from data_asn_detail where asn_id = #{asnId} ")
|
@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 任务号
|
* @param no 任务号
|
||||||
* @return Asn
|
* @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);
|
Asn queryByNo(@Param("no") String no);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
package org.cpte.modules.receive.service;
|
package org.cpte.modules.receive.service;
|
||||||
|
|
||||||
import org.cpte.modules.base.entity.Item;
|
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.Point;
|
||||||
import org.cpte.modules.base.entity.Stock;
|
import org.cpte.modules.base.entity.Stock;
|
||||||
|
import org.cpte.modules.receive.entity.Asn;
|
||||||
import org.cpte.modules.receive.entity.AsnDetail;
|
import org.cpte.modules.receive.entity.AsnDetail;
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import org.cpte.modules.receive.entity.ReceiveRecord;
|
||||||
import org.cpte.modules.saiWms.request.InboundRequest;
|
import org.cpte.modules.saiWms.request.InboundRequest;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: 入库明细
|
* @Description: 入库明细
|
||||||
|
|
@ -24,4 +29,25 @@ public interface IAsnDetailService extends IService<AsnDetail> {
|
||||||
* @return List<AsnDetail>
|
* @return List<AsnDetail>
|
||||||
*/
|
*/
|
||||||
public List<AsnDetail> selectByMainId(Long mainId);
|
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;
|
package org.cpte.modules.receive.service;
|
||||||
|
|
||||||
import org.cpte.modules.base.entity.Item;
|
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.Point;
|
||||||
import org.cpte.modules.base.entity.Stock;
|
import org.cpte.modules.base.entity.Stock;
|
||||||
import org.cpte.modules.receive.entity.AsnDetail;
|
import org.cpte.modules.receive.entity.AsnDetail;
|
||||||
import org.cpte.modules.receive.entity.Asn;
|
import org.cpte.modules.receive.entity.Asn;
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import org.cpte.modules.receive.entity.ReceiveRecord;
|
||||||
import org.cpte.modules.saiWms.request.InboundRequest;
|
import org.cpte.modules.saiWms.request.InboundRequest;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -59,7 +62,7 @@ public interface IAsnService extends IService<Asn> {
|
||||||
*/
|
*/
|
||||||
Asn buildAsn(InboundRequest inboundRequest);
|
Asn buildAsn(InboundRequest inboundRequest);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建入库单明细
|
* 构建入库单明细
|
||||||
*
|
*
|
||||||
* @param inboundDetails 入库单明细
|
* @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);
|
List<AsnDetail> buildAsnDetail(List<InboundRequest.InboundDetail> inboundDetails, Map<String, Item> itemMap, Stock stock, Point srcPoint, Point dstPoint);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 收货操作
|
* 收货操作
|
||||||
*
|
*
|
||||||
* @param asnId 入库单ID
|
* @param asnId 入库单ID
|
||||||
* @param pointCode 目标库位
|
* @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;
|
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.Item;
|
||||||
|
import org.cpte.modules.base.entity.ItemKey;
|
||||||
import org.cpte.modules.base.entity.Point;
|
import org.cpte.modules.base.entity.Point;
|
||||||
import org.cpte.modules.base.entity.Stock;
|
import org.cpte.modules.base.entity.Stock;
|
||||||
import org.cpte.modules.constant.enums.AsnStatusEnum;
|
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.AsnDetail;
|
||||||
|
import org.cpte.modules.receive.entity.ReceiveRecord;
|
||||||
import org.cpte.modules.receive.mapper.AsnDetailMapper;
|
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.IAsnDetailService;
|
||||||
import org.cpte.modules.saiWms.request.InboundRequest;
|
import org.cpte.modules.saiWms.request.InboundRequest;
|
||||||
|
import org.cpte.modules.shipping.mapper.PickMapper;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: 入库明细
|
* @Description: 入库明细
|
||||||
* @author: cpte
|
* @author: cpte
|
||||||
* @Date: 2025-11-03
|
* @Date: 2025-11-03
|
||||||
* @Version: V1.0
|
* @Version: V1.0
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class AsnDetailServiceImpl extends ServiceImpl<AsnDetailMapper, AsnDetail> implements IAsnDetailService {
|
public class AsnDetailServiceImpl extends ServiceImpl<AsnDetailMapper, AsnDetail> implements IAsnDetailService {
|
||||||
|
|
||||||
@Override
|
@Autowired
|
||||||
public List<AsnDetail> selectByMainId(Long mainId) {
|
private AsnMapper asnMapper;
|
||||||
return this.baseMapper.selectByMainId(mainId);
|
|
||||||
}
|
@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 com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.cpte.modules.base.entity.Item;
|
import org.cpte.modules.base.entity.Item;
|
||||||
import org.cpte.modules.base.entity.ItemKey;
|
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.entity.ReceiveRecord;
|
||||||
import org.cpte.modules.receive.mapper.AsnDetailMapper;
|
import org.cpte.modules.receive.mapper.AsnDetailMapper;
|
||||||
import org.cpte.modules.receive.mapper.AsnMapper;
|
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.IAsnService;
|
||||||
|
import org.cpte.modules.receive.service.ReceiveProcessor;
|
||||||
import org.cpte.modules.saiWms.request.InboundRequest;
|
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.serialNumber.AsnSerialNumberRule;
|
||||||
import org.cpte.modules.utils.BatchUtil;
|
import org.cpte.modules.utils.BatchUtil;
|
||||||
import org.cpte.modules.utils.BigDecimalUtil;
|
import org.cpte.modules.utils.BigDecimalUtil;
|
||||||
|
import org.cpte.modules.utils.RedisDistributedLockUtil;
|
||||||
import org.cpte.modules.utils.SwmsLoginUtil;
|
import org.cpte.modules.utils.SwmsLoginUtil;
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
import org.jeecg.modules.openapi.mapper.OpenApiMapper;
|
import org.jeecg.modules.openapi.mapper.OpenApiMapper;
|
||||||
|
|
@ -60,41 +64,54 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnService {
|
public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnService {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private StockMapper stockMapper;
|
|
||||||
@Autowired
|
|
||||||
private PointMapper pointMapper;
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private AsnDetailMapper asnDetailMapper;
|
private AsnDetailMapper asnDetailMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private InventoryMapper inventoryMapper;
|
|
||||||
@Autowired
|
|
||||||
private OpenApiMapper openApiMapper;
|
private OpenApiMapper openApiMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private SysDictMapper sysDictMapper;
|
private SysDictMapper sysDictMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IStockService iStockService;
|
private IAsnDetailService asnDetailService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IPointService iPointService;
|
private ReceiveProcessor receiveProcessor;
|
||||||
@Autowired
|
|
||||||
private IItemKeyService iItemKeyService;
|
|
||||||
@Autowired
|
|
||||||
private IInventoryService iInventoryService;
|
|
||||||
@Autowired
|
|
||||||
private IInventoryLogService iInventoryLogService;
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SwmsLoginUtil swmsLoginUtil;
|
private SwmsLoginUtil swmsLoginUtil;
|
||||||
@Autowired
|
@Autowired
|
||||||
private BatchUtil batchUtil;
|
|
||||||
@Autowired
|
|
||||||
private AsnSerialNumberRule asnSerialNumberRule;
|
private AsnSerialNumberRule asnSerialNumberRule;
|
||||||
|
@Autowired
|
||||||
|
private RedisDistributedLockUtil redissonLock;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public void saveMain(Asn asn, List<AsnDetail> asnDetailList) {
|
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();
|
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||||
asn.setTenantId(Long.parseLong(sysUser.getRelTenantIds()));
|
asn.setTenantId(Long.parseLong(sysUser.getRelTenantIds()));
|
||||||
asn.setSysOrgCode(sysUser.getOrgCode());
|
asn.setSysOrgCode(sysUser.getOrgCode());
|
||||||
|
String orderNo = asnSerialNumberRule.generateSerialNumber(GeneralConstant.ASN_ORDER_NO);
|
||||||
|
asn.setOrderNo(orderNo);
|
||||||
this.baseMapper.insert(asn);
|
this.baseMapper.insert(asn);
|
||||||
|
|
||||||
if (asnDetailList == null || asnDetailList.isEmpty()) {
|
if (asnDetailList == null || asnDetailList.isEmpty()) {
|
||||||
|
|
@ -112,43 +129,9 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
|
||||||
asnDetailMapper.insert(entity);
|
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
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@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
|
@Override
|
||||||
public Asn buildAsn(InboundRequest inboundRequest) {
|
public Asn buildAsn(InboundRequest inboundRequest) {
|
||||||
String orderNo = asnSerialNumberRule.generateSerialNumber(GeneralConstant.ASN_ORDER_NO);
|
|
||||||
return Asn.builder()
|
return Asn.builder()
|
||||||
.orderNo(orderNo)
|
|
||||||
.thirdOrderNo(inboundRequest.getOrderNo())
|
.thirdOrderNo(inboundRequest.getOrderNo())
|
||||||
.no(inboundRequest.getNo())
|
.no(inboundRequest.getNo())
|
||||||
.whCode(inboundRequest.getWhCode())
|
.whCode(inboundRequest.getWhCode())
|
||||||
|
|
@ -236,7 +217,7 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
|
||||||
.receivedQty(BigDecimal.ZERO)
|
.receivedQty(BigDecimal.ZERO)
|
||||||
.stockId(stock.getId())
|
.stockId(stock.getId())
|
||||||
.fromPointId(srcPoint == null ? null : srcPoint.getId())
|
.fromPointId(srcPoint == null ? null : srcPoint.getId())
|
||||||
.toPointId(dstPoint.getId())
|
.toPointId(dstPoint == null ? null : dstPoint.getId())
|
||||||
.status(AsnStatusEnum.CREATED.getValue())
|
.status(AsnStatusEnum.CREATED.getValue())
|
||||||
.project(detail.getProject())
|
.project(detail.getProject())
|
||||||
.taskNo(detail.getTaskNo())
|
.taskNo(detail.getTaskNo())
|
||||||
|
|
@ -249,80 +230,24 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
public void receiveAsn(Long asnId, String pointCode) {
|
||||||
public void receiveGoods(Long asnId, String pointCode) {
|
String lockKey = "asn:" + asnId;
|
||||||
//入库任务
|
String lockValue = null;
|
||||||
Asn asn = this.baseMapper.selectById(asnId);
|
try {
|
||||||
if (asn == null) {
|
lockValue = redissonLock.tryLock(lockKey, 10);
|
||||||
throw new RuntimeException("未匹配到入库任务【" + asnId + "】");
|
if (StringUtils.isEmpty(lockValue)) {
|
||||||
}
|
throw new RuntimeException("收货处理中,请稍后重试");
|
||||||
List<AsnDetail> asnDetails = asnDetailMapper.selectByMainId(asnId);
|
}
|
||||||
//验证容器是否入库
|
receiveProcessor.receiveAsn(asnId, pointCode);
|
||||||
Stock stock = stockMapper.selectById(asnDetails.get(0).getStockId());
|
} catch (Exception e) {
|
||||||
if (inventoryMapper.queryByStockId(stock.getId()) != null) {
|
throw new RuntimeException(e);
|
||||||
throw new RuntimeException("【" + stock.getStockCode() + "】托盘已入库");
|
} finally {
|
||||||
}
|
if (StringUtils.isNotEmpty(lockValue)) {
|
||||||
|
redissonLock.unlock(lockKey, lockValue);
|
||||||
//入库实际库位
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
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) {
|
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.setNo(asn.getNo());
|
||||||
task.setOrderNo(asn.getThirdOrderNo());
|
task.setOrderNo(asn.getThirdOrderNo());
|
||||||
task.setState(5);
|
task.setState(5);
|
||||||
|
|
@ -343,17 +268,17 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
|
||||||
task.setIsDelete(false);
|
task.setIsDelete(false);
|
||||||
task.setLastUpdateDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
|
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));
|
parameterValue1.setValue(List.of(task));
|
||||||
|
|
||||||
SaiWmsRequest.ParameterValue2 parameterValue2 = new SaiWmsRequest.ParameterValue2();
|
SMOMRequest.ParameterValue2 parameterValue2 = new SMOMRequest.ParameterValue2();
|
||||||
parameterValue2.setValue(1);
|
parameterValue2.setValue(1);
|
||||||
|
|
||||||
SaiWmsRequest.Context context = new SaiWmsRequest.Context();
|
SMOMRequest.Context context = new SMOMRequest.Context();
|
||||||
context.setInvOrgId(1);
|
context.setInvOrgId(1);
|
||||||
context.setTicket(ticket);
|
context.setTicket(ticket);
|
||||||
|
|
||||||
SaiWmsRequest saiWmsRequest = new SaiWmsRequest();
|
SMOMRequest saiWmsRequest = new SMOMRequest();
|
||||||
saiWmsRequest.setApiType("SmomWebApiController");
|
saiWmsRequest.setApiType("SmomWebApiController");
|
||||||
saiWmsRequest.setParameters(List.of(parameterValue1, parameterValue2));
|
saiWmsRequest.setParameters(List.of(parameterValue1, parameterValue2));
|
||||||
saiWmsRequest.setMethod("AutomatedWarehouseTasks");
|
saiWmsRequest.setMethod("AutomatedWarehouseTasks");
|
||||||
|
|
@ -434,26 +359,4 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
|
||||||
asnDetailMapper.updateById(updateToAsnDetail);
|
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.InboundRequest;
|
||||||
import org.cpte.modules.saiWms.request.OutboundRequest;
|
import org.cpte.modules.saiWms.request.OutboundRequest;
|
||||||
import org.cpte.modules.saiWms.request.SyncStockRequest;
|
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.api.vo.Result;
|
||||||
import org.jeecg.common.aspect.annotation.AutoLog;
|
import org.jeecg.common.aspect.annotation.AutoLog;
|
||||||
import org.jeecg.config.shiro.IgnoreAuth;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
|
@ -24,7 +23,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
public class SaiWmsController {
|
public class SaiWmsController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISaiWmsService iSaiWmsService;
|
private ISMOMService iSaiWmsService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 容器同步
|
* 容器同步
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ public class OutboundRequest {
|
||||||
// 数量
|
// 数量
|
||||||
@NotNull(message = "数量不能为空")
|
@NotNull(message = "数量不能为空")
|
||||||
@JsonProperty("Qty")
|
@JsonProperty("Qty")
|
||||||
private BigDecimal qty;
|
private Double qty;
|
||||||
|
|
||||||
// 项目号
|
// 项目号
|
||||||
@JsonProperty("Project")
|
@JsonProperty("Project")
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import lombok.Data;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class SaiWmsRequest {
|
public class SMOMRequest {
|
||||||
@JsonProperty("ApiType")
|
@JsonProperty("ApiType")
|
||||||
private String 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.OutboundRequest;
|
||||||
import org.cpte.modules.saiWms.request.SyncStockRequest;
|
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.apache.commons.lang3.StringUtils;
|
||||||
import org.cpte.modules.constant.GeneralConstant;
|
import org.cpte.modules.constant.GeneralConstant;
|
||||||
import org.cpte.modules.serialNumber.PickSerialNumberRule;
|
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.ExcelImportUtil;
|
||||||
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
|
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
|
||||||
import org.jeecgframework.poi.excel.entity.ExportParams;
|
import org.jeecgframework.poi.excel.entity.ExportParams;
|
||||||
|
|
@ -57,6 +59,8 @@ public class PickController {
|
||||||
@Autowired
|
@Autowired
|
||||||
private IPickDetailService pickDetailService;
|
private IPickDetailService pickDetailService;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private ITaskService taskService;
|
||||||
|
@Autowired
|
||||||
private PickSerialNumberRule pickSerialNumberRule;
|
private PickSerialNumberRule pickSerialNumberRule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -183,6 +187,13 @@ public class PickController {
|
||||||
return Result.OK(pickDetailList);
|
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
|
* 导出excel
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,11 @@ public class Task implements Serializable {
|
||||||
@Schema(description = "出库明细ID")
|
@Schema(description = "出库明细ID")
|
||||||
@JsonSerialize(using = ToStringSerializer.class)
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
private java.lang.Long pickDetailId;
|
private java.lang.Long pickDetailId;
|
||||||
|
|
||||||
|
@Schema(description = "商品属性ID")
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private java.lang.Long itemKeyId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 库存ID
|
* 库存ID
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ public interface PickMapper extends BaseMapper<Pick> {
|
||||||
* @param no 任务号
|
* @param no 任务号
|
||||||
* @return Pick
|
* @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);
|
Pick queryByNo(@Param("no") String no);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,14 @@ public interface TaskMapper extends BaseMapper<Task> {
|
||||||
*
|
*
|
||||||
* @return List<Pick>
|
* @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();
|
List<Task> queryUnallocatedTask();
|
||||||
|
|
||||||
@Select("SELECT * FROM data_task WHERE agv_task_id = #{agvTaskId} ")
|
@Select("SELECT * FROM data_task WHERE agv_task_id = #{agvTaskId} ")
|
||||||
List<Task> queryByAgvTask(@Param("agvTaskId") Long agvTaskId);
|
List<Task> queryByAgvTask(@Param("agvTaskId") Long agvTaskId);
|
||||||
|
|
||||||
List<Task> queryByPickIds(@Param("pickIds") List<Long> pickIds);
|
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;
|
package org.cpte.modules.shipping.service;
|
||||||
|
|
||||||
|
import org.cpte.modules.shipping.entity.Pick;
|
||||||
import org.cpte.modules.shipping.entity.PickDetail;
|
import org.cpte.modules.shipping.entity.PickDetail;
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: 出库明细
|
* @Description: 出库明细
|
||||||
|
|
@ -19,4 +21,22 @@ public interface IPickDetailService extends IService<PickDetail> {
|
||||||
* @return List<PickDetail>
|
* @return List<PickDetail>
|
||||||
*/
|
*/
|
||||||
public List<PickDetail> selectByMainId(Long mainId);
|
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 物料
|
* @param exitItemMap 物料
|
||||||
* @return 出库单明细
|
* @return 出库单明细
|
||||||
*/
|
*/
|
||||||
List<PickDetail> buildPickDetail(List<OutboundRequest.OutboundDetail> details, Map<String, Item> exitItemMap);
|
List<PickDetail> buildPickDetail(List<OutboundRequest.OutboundDetail> details, Map<String, Item> exitItemMap);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分配出库单
|
* 分配出库单
|
||||||
*
|
*
|
||||||
|
|
@ -75,7 +76,6 @@ public interface IPickService extends IService<Pick> {
|
||||||
*/
|
*/
|
||||||
List<String> allocatePick(List<Long> pickIds);
|
List<String> allocatePick(List<Long> pickIds);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消分配
|
* 取消分配
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ public interface ITaskService extends IService<Task> {
|
||||||
* @param izAll 是否整托
|
* @param izAll 是否整托
|
||||||
* @return Task
|
* @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出库任务
|
* 根据Task集合生成AGV出库任务
|
||||||
|
|
@ -65,4 +65,5 @@ public interface ITaskService extends IService<Task> {
|
||||||
*/
|
*/
|
||||||
List<Task> bulidMoveTask(List<Long> movePointIds);
|
List<Task> bulidMoveTask(List<Long> movePointIds);
|
||||||
|
|
||||||
|
List<Task> queryTaskByMainId(Long id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,109 @@
|
||||||
package org.cpte.modules.shipping.service.impl;
|
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.entity.PickDetail;
|
||||||
import org.cpte.modules.shipping.mapper.PickDetailMapper;
|
import org.cpte.modules.shipping.mapper.PickDetailMapper;
|
||||||
|
import org.cpte.modules.shipping.mapper.PickMapper;
|
||||||
import org.cpte.modules.shipping.service.IPickDetailService;
|
import org.cpte.modules.shipping.service.IPickDetailService;
|
||||||
import org.springframework.stereotype.Service;
|
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 com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: 出库明细
|
* @Description: 出库明细
|
||||||
* @author: cpte
|
* @author: cpte
|
||||||
* @Date: 2025-11-14
|
* @Date: 2025-11-14
|
||||||
* @Version: V1.0
|
* @Version: V1.0
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class PickDetailServiceImpl extends ServiceImpl<PickDetailMapper, PickDetail> implements IPickDetailService {
|
public class PickDetailServiceImpl extends ServiceImpl<PickDetailMapper, PickDetail> implements IPickDetailService {
|
||||||
|
@Autowired
|
||||||
|
private PickMapper pickMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PickDetail> selectByMainId(Long mainId) {
|
public List<PickDetail> selectByMainId(Long mainId) {
|
||||||
return this.baseMapper.selectByMainId(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.collections4.CollectionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.shiro.SecurityUtils;
|
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.constant.GeneralConstant;
|
||||||
import org.cpte.modules.base.entity.Item;
|
import org.cpte.modules.base.entity.Item;
|
||||||
import org.cpte.modules.base.entity.Point;
|
import org.cpte.modules.base.entity.Point;
|
||||||
import org.cpte.modules.base.entity.Stock;
|
|
||||||
import org.cpte.modules.base.service.IItemService;
|
|
||||||
import org.cpte.modules.base.service.IPointService;
|
|
||||||
import org.cpte.modules.base.service.IStockService;
|
|
||||||
import org.cpte.modules.constant.enums.*;
|
import org.cpte.modules.constant.enums.*;
|
||||||
import org.cpte.modules.inventory.entity.Inventory;
|
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.inventory.service.IInventoryService;
|
||||||
import org.cpte.modules.inventoryLog.service.IInventoryLogService;
|
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.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.serialNumber.PickSerialNumberRule;
|
||||||
import org.cpte.modules.shipping.entity.Pick;
|
import org.cpte.modules.shipping.entity.Pick;
|
||||||
import org.cpte.modules.shipping.entity.PickDetail;
|
import org.cpte.modules.shipping.entity.PickDetail;
|
||||||
|
|
@ -30,13 +25,13 @@ import org.cpte.modules.shipping.entity.Task;
|
||||||
import org.cpte.modules.shipping.mapper.PickDetailMapper;
|
import org.cpte.modules.shipping.mapper.PickDetailMapper;
|
||||||
import org.cpte.modules.shipping.mapper.PickMapper;
|
import org.cpte.modules.shipping.mapper.PickMapper;
|
||||||
import org.cpte.modules.shipping.mapper.TaskMapper;
|
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.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.BatchUtil;
|
||||||
import org.cpte.modules.utils.BigDecimalUtil;
|
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.cpte.modules.utils.SwmsLoginUtil;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
|
|
@ -53,7 +48,6 @@ import java.math.BigDecimal;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: 出库单
|
* @Description: 出库单
|
||||||
|
|
@ -70,51 +64,29 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
|
||||||
@Autowired
|
@Autowired
|
||||||
private TaskMapper taskMapper;
|
private TaskMapper taskMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private ItemKeyMapper itemKeyMapper;
|
|
||||||
@Autowired
|
|
||||||
private InventoryMapper inventoryMapper;
|
|
||||||
@Autowired
|
|
||||||
private SysDictMapper sysDictMapper;
|
private SysDictMapper sysDictMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private OpenApiMapper openApiMapper;
|
private OpenApiMapper openApiMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IItemService iItemService;
|
private IPickDetailService pickDetailService;
|
||||||
@Autowired
|
|
||||||
private IStockService iStockService;
|
|
||||||
@Autowired
|
|
||||||
private IPointService iPointService;
|
|
||||||
@Autowired
|
|
||||||
private ITaskService iTaskService;
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IInventoryService inventoryService;
|
private IInventoryService inventoryService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IInventoryLogService iInventoryLogService;
|
private IInventoryLogService inventoryLogService;
|
||||||
@Autowired
|
|
||||||
private SwmsLoginUtil swmsLoginUtil;
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private BaseCommonService baseCommonService;
|
private BaseCommonService baseCommonService;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private SwmsLoginUtil swmsLoginUtil;
|
||||||
|
@Autowired
|
||||||
private BatchUtil batchUtils;
|
private BatchUtil batchUtils;
|
||||||
@Autowired
|
@Autowired
|
||||||
private PickSerialNumberRule pickSerialNumberRule;
|
private PickSerialNumberRule pickSerialNumberRule;
|
||||||
|
@Autowired
|
||||||
/**
|
private AllocateProcessor allocateProcessor;
|
||||||
* 获取出库单Map
|
@Autowired
|
||||||
*
|
private CancelAllocateProcessor cancelAllocateProcessor;
|
||||||
* @param pickIds 出库单id
|
@Autowired
|
||||||
* @return 出库单Map
|
private RedisDistributedLockUtil redissonLock;
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取出库单明细Map
|
* 获取出库单明细Map
|
||||||
|
|
@ -134,25 +106,37 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
|
||||||
return pickDetailMap;
|
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
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public void saveMain(Pick pick, List<PickDetail> pickDetailList) {
|
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();
|
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||||
pick.setTenantId(Long.parseLong(sysUser.getRelTenantIds()));
|
pick.setTenantId(Long.parseLong(sysUser.getRelTenantIds()));
|
||||||
pick.setSysOrgCode(sysUser.getOrgCode());
|
pick.setSysOrgCode(sysUser.getOrgCode());
|
||||||
|
String orderNo = pickSerialNumberRule.generateSerialNumber(GeneralConstant.PICK_ORDER_NO);
|
||||||
|
pick.setOrderNo(orderNo);
|
||||||
this.baseMapper.insert(pick);
|
this.baseMapper.insert(pick);
|
||||||
|
|
||||||
if (pickDetailList == null || pickDetailList.isEmpty()) {
|
if (pickDetailList == null || pickDetailList.isEmpty()) {
|
||||||
|
|
@ -173,67 +157,9 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
|
||||||
pickDetailMapper.insert(entity);
|
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
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@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
|
@Override
|
||||||
|
|
@ -311,9 +237,7 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pick buildPick(OutboundRequest outboundRequest) {
|
public Pick buildPick(OutboundRequest outboundRequest) {
|
||||||
String orderNo = pickSerialNumberRule.generateSerialNumber(GeneralConstant.PICK_ORDER_NO);
|
|
||||||
return Pick.builder()
|
return Pick.builder()
|
||||||
.orderNo(orderNo)
|
|
||||||
.thirdOrderNo(outboundRequest.getOrderNo())
|
.thirdOrderNo(outboundRequest.getOrderNo())
|
||||||
.no(outboundRequest.getNo())
|
.no(outboundRequest.getNo())
|
||||||
.whCode(outboundRequest.getWhCode())
|
.whCode(outboundRequest.getWhCode())
|
||||||
|
|
@ -332,7 +256,7 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
|
||||||
.lineNo(Integer.parseInt(detail.getLineNo()))
|
.lineNo(Integer.parseInt(detail.getLineNo()))
|
||||||
.itemId(exitItemMap.get(detail.getItem()).getId())
|
.itemId(exitItemMap.get(detail.getItem()).getId())
|
||||||
.unit(detail.getUnit())
|
.unit(detail.getUnit())
|
||||||
.orderQty(detail.getQty())
|
.orderQty(BigDecimal.valueOf(detail.getQty()))
|
||||||
.allocatedQty(BigDecimal.ZERO)
|
.allocatedQty(BigDecimal.ZERO)
|
||||||
.pickedQty(BigDecimal.ZERO)
|
.pickedQty(BigDecimal.ZERO)
|
||||||
.status(PickStatusEnum.CREATED.getValue())
|
.status(PickStatusEnum.CREATED.getValue())
|
||||||
|
|
@ -347,400 +271,18 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public List<String> allocatePick(List<Long> pickIds) {
|
public List<String> allocatePick(List<Long> pickIds) {
|
||||||
// 错误信息去重(LinkedHashSet保证顺序和唯一)
|
return allocateProcessor.allocatePick(pickIds);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算库存得分
|
|
||||||
*
|
|
||||||
* @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
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public List<String> cancelAllocate(List<Long> pickIds) {
|
public List<String> cancelAllocate(List<Long> pickIds) {
|
||||||
Set<String> errorMsgSet = new LinkedHashSet<>();
|
return cancelAllocateProcessor.cancelAllocatePick(pickIds);
|
||||||
// -------------------------- 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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void pickTask(List<Task> tasks, Point endPoint) {
|
public void pickTask(List<Task> tasks, Point endPoint) {
|
||||||
if (CollectionUtils.isEmpty(tasks)) {
|
|
||||||
throw new RuntimeException("无拣货任务");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ================= 1. 数据准备 (批量查询) =================
|
// ================= 1. 数据准备 (批量查询) =================
|
||||||
// 1.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();
|
List<Long> inventoryIds = tasks.stream().map(Task::getInventoryId).distinct().toList();
|
||||||
|
|
||||||
// 1.2 构建映射 Map
|
// 1.2 构建映射 Map
|
||||||
Map<Long, Pick> pickMap = queryByPickIdsToMap(pickIds);
|
Map<Long, Pick> pickMap = pickDetailService.queryByPickIdsToMap(pickIds);
|
||||||
Map<Long, PickDetail> pickDetailMap = queryByPickDetailIdsToMap(pickDetailIds);
|
Map<Long, PickDetail> pickDetailMap = queryByPickDetailIdsToMap(pickDetailIds);
|
||||||
Map<Long, Inventory> inventoryMap = inventoryService.queryByInventoryIdsToMap(inventoryIds);
|
Map<Long, Inventory> inventoryMap = inventoryService.queryByInventoryIdsToMap(inventoryIds);
|
||||||
|
|
||||||
|
|
@ -791,7 +333,7 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
|
||||||
deleteToInventoryIds.add(inventory.getId());
|
deleteToInventoryIds.add(inventory.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
iInventoryLogService.addPickInventoryLog(inventory, pickedQty, pick.getOrderNo(), task.getPickDetailId(), null);
|
inventoryLogService.addPickInventoryLog(inventory, pickedQty, pick.getOrderNo(), task.getPickDetailId(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------- 4. 批量操作 --------------------------
|
// -------------------------- 4. 批量操作 --------------------------
|
||||||
|
|
@ -834,7 +376,7 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
|
||||||
// 刷新时重新查询最新的数据
|
// 刷新时重新查询最新的数据
|
||||||
List<PickDetail> latestPickDetails = pickDetailMapper.selectByMainId(pick.getId());
|
List<PickDetail> latestPickDetails = pickDetailMapper.selectByMainId(pick.getId());
|
||||||
pickDetailsCache.put(pickId, latestPickDetails); // 更新缓存
|
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
|
* 出库库任务回传JSON
|
||||||
*/
|
*/
|
||||||
private String pickTaskCallbackJson(Pick pick, PickDetail pickDetail, Task task, Integer state, String ticket) {
|
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.setNo(pick.getNo());
|
||||||
taskReq.setOrderNo(pick.getThirdOrderNo());
|
taskReq.setOrderNo(pick.getThirdOrderNo());
|
||||||
taskReq.setState(state);
|
taskReq.setState(state);
|
||||||
|
|
||||||
SaiWmsRequest.ShipmentFeedbackDetail shipmentFeedbackDetail = new SaiWmsRequest.ShipmentFeedbackDetail();
|
SMOMRequest.ShipmentFeedbackDetail shipmentFeedbackDetail = new SMOMRequest.ShipmentFeedbackDetail();
|
||||||
shipmentFeedbackDetail.setLineNo(String.valueOf(pickDetail.getLineNo()));
|
shipmentFeedbackDetail.setLineNo(String.valueOf(pickDetail.getLineNo()));
|
||||||
shipmentFeedbackDetail.setLpn(task.getStockCode());
|
shipmentFeedbackDetail.setLpn(task.getStockCode());
|
||||||
shipmentFeedbackDetail.setQty(task.getPlanQty().intValue());
|
shipmentFeedbackDetail.setQty(task.getPlanQty().intValue());
|
||||||
|
|
@ -869,17 +411,17 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
|
||||||
taskReq.setLastUpdateDate(lastUpdateDate);
|
taskReq.setLastUpdateDate(lastUpdateDate);
|
||||||
|
|
||||||
|
|
||||||
SaiWmsRequest.ParameterValue1 parameterValue1 = new SaiWmsRequest.ParameterValue1();
|
SMOMRequest.ParameterValue1 parameterValue1 = new SMOMRequest.ParameterValue1();
|
||||||
parameterValue1.setValue(List.of(taskReq));
|
parameterValue1.setValue(List.of(taskReq));
|
||||||
|
|
||||||
SaiWmsRequest.ParameterValue2 parameterValue2 = new SaiWmsRequest.ParameterValue2();
|
SMOMRequest.ParameterValue2 parameterValue2 = new SMOMRequest.ParameterValue2();
|
||||||
parameterValue2.setValue(1);
|
parameterValue2.setValue(1);
|
||||||
|
|
||||||
SaiWmsRequest.Context context = new SaiWmsRequest.Context();
|
SMOMRequest.Context context = new SMOMRequest.Context();
|
||||||
context.setInvOrgId(1);
|
context.setInvOrgId(1);
|
||||||
context.setTicket(ticket);
|
context.setTicket(ticket);
|
||||||
|
|
||||||
SaiWmsRequest saiWmsRequest = new SaiWmsRequest();
|
SMOMRequest saiWmsRequest = new SMOMRequest();
|
||||||
saiWmsRequest.setApiType("SmomWebApiController");
|
saiWmsRequest.setApiType("SmomWebApiController");
|
||||||
saiWmsRequest.setParameters(List.of(parameterValue1, parameterValue2));
|
saiWmsRequest.setParameters(List.of(parameterValue1, parameterValue2));
|
||||||
saiWmsRequest.setMethod("OutboundTaskCallbackInterface");
|
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) {
|
public void pickTaskCallback(Pick pick, PickDetail pickDetail, Task task, Integer state) {
|
||||||
// 检查接口开关, 未开启则返回
|
// 检查接口开关, 未开启则返回
|
||||||
if (sysDictMapper.queryByDictCode(GeneralConstant.OPEN_FLAG) == null) {
|
if (sysDictMapper.queryByDictCode(GeneralConstant.OPEN_FLAG) == null) {
|
||||||
updatePickDetailResponse(pickDetail,task, GeneralConstant.SMOM_FAIL_CODE, "接口未开启");
|
updatePickDetailResponse(pickDetail, task, GeneralConstant.SMOM_FAIL_CODE, "接口未开启");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -912,9 +454,9 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
|
||||||
String url = openApiMapper.getRequestUrl(GeneralConstant.OUTBOUND_CALLBACK).getOriginUrl();
|
String url = openApiMapper.getRequestUrl(GeneralConstant.OUTBOUND_CALLBACK).getOriginUrl();
|
||||||
JSONObject jsonObject = swmsLoginUtil.sendSMOMResponse(json, url, authorization);
|
JSONObject jsonObject = swmsLoginUtil.sendSMOMResponse(json, url, authorization);
|
||||||
String code = validateResponse(jsonObject);
|
String code = validateResponse(jsonObject);
|
||||||
updatePickDetailResponse(pickDetail,task, code, jsonObject.toJSONString());
|
updatePickDetailResponse(pickDetail, task, code, jsonObject.toJSONString());
|
||||||
} catch (Exception e) {
|
} 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 task 出库任务
|
||||||
* @param message 信息
|
* @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 (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());
|
pickDetail.setStatus(PickStatusEnum.CLOSED.getValue());
|
||||||
pickDetailMapper.updateById(pickDetail);
|
pickDetailMapper.updateById(pickDetail);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements IT
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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;
|
LoginUser sysUser = null;
|
||||||
try {
|
try {
|
||||||
sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||||
|
|
@ -122,6 +122,7 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements IT
|
||||||
.stockCode(stock.getStockCode())
|
.stockCode(stock.getStockCode())
|
||||||
.pickId(pickId)
|
.pickId(pickId)
|
||||||
.pickDetailId(pickDetailId)
|
.pickDetailId(pickDetailId)
|
||||||
|
.itemKeyId(itemKeyId)
|
||||||
.inventoryId(inventoryId)
|
.inventoryId(inventoryId)
|
||||||
.planQty(planQty)
|
.planQty(planQty)
|
||||||
.moveQty(BigDecimal.ZERO)
|
.moveQty(BigDecimal.ZERO)
|
||||||
|
|
@ -223,7 +224,7 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements IT
|
||||||
String taskNo = moveSerialNumberRule.generateSerialNumber(GeneralConstant.MOVE_ORDER_NO);
|
String taskNo = moveSerialNumberRule.generateSerialNumber(GeneralConstant.MOVE_ORDER_NO);
|
||||||
//根据算法找到最优的目标库位
|
//根据算法找到最优的目标库位
|
||||||
Point toPoint=null;
|
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);
|
moveList.add(moveTask);
|
||||||
log.info("生成移位任务:{}- 容器:{} - 库位:{} - 库存数量:{}", taskNo, stock.getStockCode(), fromPoint.getPointCode(), inv.getQuantity());
|
log.info("生成移位任务:{}- 容器:{} - 库位:{} - 库存数量:{}", taskNo, stock.getStockCode(), fromPoint.getPointCode(), inv.getQuantity());
|
||||||
inv.setStatus(InventoryStatusEnum.TRANSFER.getValue());
|
inv.setStatus(InventoryStatusEnum.TRANSFER.getValue());
|
||||||
|
|
@ -232,6 +233,11 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements IT
|
||||||
return moveList;
|
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
|
@AllArgsConstructor
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class InventoryGroupKey {
|
public class ItemGroupKey {
|
||||||
private Long itemId;//物料
|
private Long itemId;//物料
|
||||||
private String whCode; // 仓库代码
|
private String whCode; // 仓库代码
|
||||||
private String project;//项目号
|
private String project;//项目号
|
||||||
|
|
@ -19,8 +19,8 @@ public class InventoryGroupKey {
|
||||||
private String propC3; // 库存状态
|
private String propC3; // 库存状态
|
||||||
|
|
||||||
|
|
||||||
public static InventoryGroupKey of(Long itemId, String whCode, String project, String taskNo, String propC1, String propC3) {
|
public static ItemGroupKey of(Long itemId, String whCode, String project, String taskNo, String propC1, String propC3) {
|
||||||
return new InventoryGroupKey(
|
return new ItemGroupKey(
|
||||||
itemId,
|
itemId,
|
||||||
StringUtils.defaultIfBlank(whCode, ""),
|
StringUtils.defaultIfBlank(whCode, ""),
|
||||||
StringUtils.defaultIfBlank(project, ""),
|
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.CancelTaskRequest;
|
||||||
import org.cpte.modules.tesAgv.request.NewMovePodTaskRequest;
|
import org.cpte.modules.tesAgv.request.NewMovePodTaskRequest;
|
||||||
import org.cpte.modules.tesAgv.request.TesCallbackRequest;
|
import org.cpte.modules.tesAgv.request.TesCallbackRequest;
|
||||||
import org.cpte.modules.tesAgv.response.TesResult;
|
|
||||||
import org.cpte.modules.tesAgv.service.ITesAgvService;
|
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.common.aspect.annotation.AutoLog;
|
||||||
import org.jeecg.config.shiro.IgnoreAuth;
|
import org.jeecg.config.shiro.IgnoreAuth;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
|
||||||
|
|
@ -49,19 +49,19 @@ public class ITesAgvServiceImpl implements ITesAgvService {
|
||||||
private OpenApiMapper openApiMapper;
|
private OpenApiMapper openApiMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IPointService iPointService;
|
private IPointService pointService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IStockService iStockService;
|
private IStockService stockService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IAsnService iAsnService;
|
private IAsnService asnService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IPickService iPickService;
|
private IPickService pickService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IAgvTaskService iAgvTaskService;
|
private IAgvTaskService agvTaskService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generateTesAgvTaskJson(AgvTask agvTask) {
|
public String generateTesAgvTaskJson(AgvTask agvTask) {
|
||||||
|
|
@ -229,23 +229,23 @@ public class ITesAgvServiceImpl implements ITesAgvService {
|
||||||
private void handleEnd(Long asnId, AgvTask agvTask) {
|
private void handleEnd(Long asnId, AgvTask agvTask) {
|
||||||
if (BusinessTypeEnum.INBOUND.getValue().equals(agvTask.getType())) {
|
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())) {
|
} 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());
|
List<Task> tasks = taskMapper.queryByAgvTask(agvTask.getId());
|
||||||
iPickService.pickTask(tasks, endPoint);
|
pickService.pickTask(tasks, endPoint);
|
||||||
Point startPoint = iPointService.validatePoint(agvTask.getStartCode());
|
Point startPoint = pointService.validatePoint(agvTask.getStartCode());
|
||||||
|
|
||||||
Stock stock = iStockService.validateStock(agvTask.getCarrierCode());
|
Stock stock = stockService.validateStock(agvTask.getCarrierCode());
|
||||||
iPointService.unbindPoint(startPoint);
|
pointService.unbindPoint(startPoint);
|
||||||
iStockService.bindStock(stock, endPoint);
|
stockService.bindStock(stock, endPoint);
|
||||||
|
|
||||||
//判断AGV任务是否整托,整托生成AGV搬运任务
|
//判断AGV任务是否整托,整托生成AGV搬运任务
|
||||||
if (agvTask.getIzAll() == 0) {
|
if (agvTask.getIzAll() == 0) {
|
||||||
//查询电梯口点位作为目标点位
|
//查询电梯口点位作为目标点位
|
||||||
String endCode = iPointService.getElevatorPoint(agvTask.getEndCode(), GeneralConstant.CK_ELEVATOR_TASK_INDEX);
|
String endCode = pointService.getElevatorPoint(agvTask.getEndCode(), GeneralConstant.CK_ELEVATOR_TASK_INDEX);
|
||||||
iAgvTaskService.createAgvTask(null, agvTask.getCarrierCode(), agvTask.getEndCode(), endCode, null, BusinessTypeEnum.OUTBOUND.getValue(), 0,AgvVendorEnum.HIK.getValue());
|
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 任务
|
* @param agvTask 任务
|
||||||
*/
|
*/
|
||||||
private void handleResend(AgvTask agvTask) {
|
private void handleResend(AgvTask agvTask) {
|
||||||
Long count = agvTaskMapper.existsByStockCode(agvTask.getCarrierCode(), AgvVendorEnum.TES.getValue());
|
if (agvTaskMapper.existsByStockCode(agvTask.getCarrierCode(), AgvVendorEnum.TES.getValue()) != null) {
|
||||||
if (count > 0) {
|
|
||||||
throw new RuntimeException("任务已重新生成,请勿重复操作! ");
|
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()) {
|
switch (agvTask.getType()) {
|
||||||
case "INBOUND":
|
case "INBOUND":
|
||||||
case "OUTBOUND":
|
case "OUTBOUND":
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import org.cpte.modules.shipping.entity.Task;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
|
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ import java.sql.Timestamp;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
@Service
|
@Component
|
||||||
public class BatchUtil {
|
public class BatchUtil {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
@ -286,8 +287,8 @@ public class BatchUtil {
|
||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public void saveBatchTask(List<Task> tasks) {
|
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) " +
|
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 (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
|
||||||
batchInsert(sql, tasks, (ps, task) -> {
|
batchInsert(sql, tasks, (ps, task) -> {
|
||||||
try {
|
try {
|
||||||
ps.setLong(1, IdWorker.getId());
|
ps.setLong(1, IdWorker.getId());
|
||||||
|
|
@ -302,16 +303,17 @@ public class BatchUtil {
|
||||||
ps.setString(10, task.getStockCode());
|
ps.setString(10, task.getStockCode());
|
||||||
ps.setLong(11, task.getPickId());
|
ps.setLong(11, task.getPickId());
|
||||||
ps.setLong(12, task.getPickDetailId());
|
ps.setLong(12, task.getPickDetailId());
|
||||||
ps.setLong(13, task.getInventoryId());
|
ps.setLong(13, task.getItemKeyId());
|
||||||
ps.setBigDecimal(14, task.getPlanQty());
|
ps.setLong(14, task.getInventoryId());
|
||||||
ps.setBigDecimal(15, task.getMoveQty());
|
ps.setBigDecimal(15, task.getPlanQty());
|
||||||
ps.setInt(16, task.getTaskType());
|
ps.setBigDecimal(16, task.getMoveQty());
|
||||||
ps.setInt(17, task.getTaskStatus());
|
ps.setInt(17, task.getTaskType());
|
||||||
ps.setInt(18, task.getIzAll());
|
ps.setInt(18, task.getTaskStatus());
|
||||||
ps.setString(19, task.getSysOrgCode());
|
ps.setInt(19, task.getIzAll());
|
||||||
ps.setLong(20, task.getTenantId());
|
ps.setString(20, task.getSysOrgCode());
|
||||||
ps.setString(21, task.getCreateBy());
|
ps.setLong(21, task.getTenantId());
|
||||||
ps.setTimestamp(22, new Timestamp(task.getCreateTime().getTime()));
|
ps.setString(22, task.getCreateBy());
|
||||||
|
ps.setTimestamp(23, new Timestamp(task.getCreateTime().getTime()));
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.dao.CannotAcquireLockException;
|
import org.springframework.dao.CannotAcquireLockException;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
|
@ -13,7 +14,7 @@ import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Component
|
||||||
public class CodeGeneratorUtil {
|
public class CodeGeneratorUtil {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
@ -31,13 +32,8 @@ public class CodeGeneratorUtil {
|
||||||
public String generateSerialNumber(String type) {
|
public String generateSerialNumber(String type) {
|
||||||
try {
|
try {
|
||||||
String dateStr = LocalDate.now().format(DATE_FORMATTER);
|
String dateStr = LocalDate.now().format(DATE_FORMATTER);
|
||||||
|
String sql = "SELECT current_seq FROM generator_sequence WHERE type = ? AND date_str = ? for update";
|
||||||
// 使用 SELECT for update 加锁查询并更新
|
List<Integer> result = jdbcTemplate.queryForList(sql, Integer.class, type, dateStr);
|
||||||
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);
|
|
||||||
|
|
||||||
if (result.isEmpty()) {
|
if (result.isEmpty()) {
|
||||||
// 插入初始值
|
// 插入初始值
|
||||||
String insertSql = "INSERT INTO generator_sequence (type, date_str, current_seq) VALUES (?, ?, 1)";
|
String insertSql = "INSERT INTO generator_sequence (type, date_str, current_seq) VALUES (?, ?, 1)";
|
||||||
|
|
@ -53,7 +49,7 @@ public class CodeGeneratorUtil {
|
||||||
return type + dateStr + seqStr;
|
return type + dateStr + seqStr;
|
||||||
}
|
}
|
||||||
} catch (CannotAcquireLockException e) {
|
} 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 lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.cpte.modules.constant.GeneralConstant;
|
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.entity.OpenApi;
|
||||||
import org.jeecg.modules.openapi.mapper.OpenApiMapper;
|
import org.jeecg.modules.openapi.mapper.OpenApiMapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Component
|
||||||
public class SwmsLoginUtil {
|
public class SwmsLoginUtil {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
@ -27,15 +27,15 @@ public class SwmsLoginUtil {
|
||||||
String userName = "LM";
|
String userName = "LM";
|
||||||
String password = "654321";
|
String password = "654321";
|
||||||
|
|
||||||
SaiWmsRequest.ParameterValue3 parameterValue1 = new SaiWmsRequest.ParameterValue3();
|
SMOMRequest.ParameterValue3 parameterValue1 = new SMOMRequest.ParameterValue3();
|
||||||
parameterValue1.setValue(userName);
|
parameterValue1.setValue(userName);
|
||||||
|
|
||||||
SaiWmsRequest.ParameterValue3 parameterValue2 = new SaiWmsRequest.ParameterValue3();
|
SMOMRequest.ParameterValue3 parameterValue2 = new SMOMRequest.ParameterValue3();
|
||||||
parameterValue2.setValue(password);
|
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.setApiType("AuthenticationController");
|
||||||
saiWmsRequest.setParameters(List.of(parameterValue1, parameterValue2));
|
saiWmsRequest.setParameters(List.of(parameterValue1, parameterValue2));
|
||||||
saiWmsRequest.setMethod("Login");
|
saiWmsRequest.setMethod("Login");
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,10 @@ public class test {
|
||||||
tesMap.put("ak", "ak-EMCNIpxwfMXzJ8rj");
|
tesMap.put("ak", "ak-EMCNIpxwfMXzJ8rj");
|
||||||
tesMap.put("sk", "HtT14KlSwCfLfLyGe3FeJVPc3zmjZwXR");
|
tesMap.put("sk", "HtT14KlSwCfLfLyGe3FeJVPc3zmjZwXR");
|
||||||
|
|
||||||
|
Map<String, String> agvMap = new HashMap<>();
|
||||||
|
agvMap.put("ak", "ak-EE2BXylYpsRcW9LY");
|
||||||
|
agvMap.put("sk", "20xCnGWwgh6reNqmp5CbW7WjVBHVC9nN");
|
||||||
|
|
||||||
|
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
System.out.println("sai-timestamp:" + timestamp);
|
System.out.println("sai-timestamp:" + timestamp);
|
||||||
|
|
@ -22,7 +26,9 @@ public class test {
|
||||||
System.out.println("=======================================================================================");
|
System.out.println("=======================================================================================");
|
||||||
System.out.println("tes-timestamp:" + timestamp);
|
System.out.println("tes-timestamp:" + timestamp);
|
||||||
System.out.println("tes-signature:" + md5(tesMap.get("ak")+ tesMap.get("sk") + 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) {
|
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.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.google.common.collect.Lists;
|
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.modules.openapi.swagger.*;
|
||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
|
@ -35,6 +38,7 @@ import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
@ -121,11 +125,11 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||||
*/
|
*/
|
||||||
@DeleteMapping(value = "/deleteBatch")
|
@DeleteMapping(value = "/deleteBatch")
|
||||||
public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||||
List<String> list= Arrays.asList(ids.split(","));
|
List<String> list = Arrays.asList(ids.split(","));
|
||||||
list.forEach(id->{
|
list.forEach(id -> {
|
||||||
OpenApi openApi = service.getById(id);
|
OpenApi openApi = service.getById(id);
|
||||||
redisUtil.del(openApi.getRequestUrl());
|
redisUtil.del(openApi.getRequestUrl());
|
||||||
});
|
});
|
||||||
this.service.removeByIds(Arrays.asList(ids.split(",")));
|
this.service.removeByIds(Arrays.asList(ids.split(",")));
|
||||||
return Result.ok("批量删除成功!");
|
return Result.ok("批量删除成功!");
|
||||||
}
|
}
|
||||||
|
|
@ -144,11 +148,12 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接口调用
|
* 接口调用
|
||||||
|
*
|
||||||
* @param path
|
* @param path
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@RequestMapping(value = "/call/{path}", method = {RequestMethod.GET,RequestMethod.POST})
|
@RequestMapping(value = "/call/{path}", method = {RequestMethod.GET, RequestMethod.POST})
|
||||||
public Result<?> call(@PathVariable String path, @RequestBody(required = false) String json, HttpServletRequest request) {
|
public Object call(@PathVariable String path, @RequestBody(required = false) String json, HttpServletRequest request) {
|
||||||
OpenApi openApi = service.findByPath(path);
|
OpenApi openApi = service.findByPath(path);
|
||||||
if (Objects.isNull(openApi)) {
|
if (Objects.isNull(openApi)) {
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
@ -158,8 +163,8 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||||
}
|
}
|
||||||
HttpHeaders httpHeaders = new HttpHeaders();
|
HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
if (StrUtil.isNotEmpty(openApi.getHeadersJson())) {
|
if (StrUtil.isNotEmpty(openApi.getHeadersJson())) {
|
||||||
List<OpenApiHeader> headers = JSON.parseArray(openApi.getHeadersJson(),OpenApiHeader.class);
|
List<OpenApiHeader> headers = JSON.parseArray(openApi.getHeadersJson(), OpenApiHeader.class);
|
||||||
if (headers.size()>0) {
|
if (headers.size() > 0) {
|
||||||
for (OpenApiHeader header : headers) {
|
for (OpenApiHeader header : headers) {
|
||||||
httpHeaders.put(header.getHeaderKey(), Lists.newArrayList(request.getHeader(header.getHeaderKey())));
|
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());
|
SysUser systemUser = sysUserService.getUserByName(openApiAuth.getCreateBy());
|
||||||
String token = this.getToken(systemUser.getUsername(), systemUser.getPassword());
|
String token = this.getToken(systemUser.getUsername(), systemUser.getPassword());
|
||||||
httpHeaders.put("X-Access-Token", Lists.newArrayList(token));
|
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);
|
HttpEntity<String> httpEntity = new HttpEntity<>(json, httpHeaders);
|
||||||
url = RestUtil.getBaseUrl() + url;
|
url = RestUtil.getBaseUrl() + url;
|
||||||
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
|
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
|
||||||
if (HttpMethod.GET.matches(method)
|
if (HttpMethod.GET.matches(method)
|
||||||
|| HttpMethod.DELETE.matches(method)
|
|| HttpMethod.DELETE.matches(method)
|
||||||
|
|
@ -184,16 +189,16 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||||
//拼接参数
|
//拼接参数
|
||||||
if (!request.getParameterMap().isEmpty()) {
|
if (!request.getParameterMap().isEmpty()) {
|
||||||
if (StrUtil.isNotEmpty(openApi.getParamsJson())) {
|
if (StrUtil.isNotEmpty(openApi.getParamsJson())) {
|
||||||
List<OpenApiParam> params = JSON.parseArray(openApi.getParamsJson(),OpenApiParam.class);
|
List<OpenApiParam> params = JSON.parseArray(openApi.getParamsJson(), OpenApiParam.class);
|
||||||
if (params.size()>0) {
|
if (params.size() > 0) {
|
||||||
Map<String, OpenApiParam> openApiParamMap = params.stream().collect(Collectors.toMap(p -> p.getParamKey(), p -> p, (e, r) -> e));
|
Map<String, OpenApiParam> openApiParamMap = params.stream().collect(Collectors.toMap(p -> p.getParamKey(), p -> p, (e, r) -> e));
|
||||||
request.getParameterMap().forEach((k, v) -> {
|
request.getParameterMap().forEach((k, v) -> {
|
||||||
OpenApiParam openApiParam = openApiParamMap.get(k);
|
OpenApiParam openApiParam = openApiParamMap.get(k);
|
||||||
if (Objects.nonNull(openApiParam)) {
|
if (Objects.nonNull(openApiParam)) {
|
||||||
if(v==null&&StrUtil.isNotEmpty(openApiParam.getDefaultValue())){
|
if (v == null && StrUtil.isNotEmpty(openApiParam.getDefaultValue())) {
|
||||||
builder.queryParam(openApiParam.getParamKey(), openApiParam.getDefaultValue());
|
builder.queryParam(openApiParam.getParamKey(), openApiParam.getDefaultValue());
|
||||||
}
|
}
|
||||||
if (v!=null){
|
if (v != null) {
|
||||||
builder.queryParam(openApiParam.getParamKey(), v);
|
builder.queryParam(openApiParam.getParamKey(), v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +209,17 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
URI targetUrl = builder.build().encode().toUri();
|
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.setTags(Lists.newArrayList("openapi"));
|
||||||
operation.setSummary(openApi.getName());
|
operation.setSummary(openApi.getName());
|
||||||
operation.setDescription(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"));
|
operation.setProduces(Lists.newArrayList("application/json"));
|
||||||
parameters(operation, openApi);
|
parameters(operation, openApi);
|
||||||
|
|
||||||
|
|
@ -261,19 +276,19 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||||
definition.setType("object");
|
definition.setType("object");
|
||||||
Map<String, SwaggerDefinitionProperties> definitionProperties = new HashMap<>();
|
Map<String, SwaggerDefinitionProperties> definitionProperties = new HashMap<>();
|
||||||
definition.setProperties(definitionProperties);
|
definition.setProperties(definitionProperties);
|
||||||
if (openApi.getBody()!=null){
|
if (openApi.getBody() != null) {
|
||||||
JSONObject jsonObject = JSONObject.parseObject(openApi.getBody());
|
JSONObject jsonObject = JSONObject.parseObject(openApi.getBody());
|
||||||
if (jsonObject.size()>0){
|
if (jsonObject.size() > 0) {
|
||||||
for (Map.Entry<String, Object> properties : jsonObject.entrySet()) {
|
for (Map.Entry<String, Object> properties : jsonObject.entrySet()) {
|
||||||
SwaggerDefinitionProperties swaggerDefinitionProperties = new SwaggerDefinitionProperties();
|
SwaggerDefinitionProperties swaggerDefinitionProperties = new SwaggerDefinitionProperties();
|
||||||
swaggerDefinitionProperties.setType("string");
|
swaggerDefinitionProperties.setType("string");
|
||||||
swaggerDefinitionProperties.setDescription(properties.getValue()+"");
|
swaggerDefinitionProperties.setDescription(properties.getValue() + "");
|
||||||
definitionProperties.put(properties.getKey(), swaggerDefinitionProperties);
|
definitionProperties.put(properties.getKey(), swaggerDefinitionProperties);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// body的definition构建完成
|
// body的definition构建完成
|
||||||
definitions.put(openApi.getRequestUrl()+"Using"+openApi.getRequestMethod()+"body", definition);
|
definitions.put(openApi.getRequestUrl() + "Using" + openApi.getRequestMethod() + "body", definition);
|
||||||
|
|
||||||
SwaggerOperationParameter bodyParameter = new SwaggerOperationParameter();
|
SwaggerOperationParameter bodyParameter = new SwaggerOperationParameter();
|
||||||
bodyParameter.setDescription(openApi.getName() + " body");
|
bodyParameter.setDescription(openApi.getName() + " body");
|
||||||
|
|
@ -282,7 +297,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||||
bodyParameter.setRequired(true);
|
bodyParameter.setRequired(true);
|
||||||
|
|
||||||
Map<String, String> bodySchema = new HashMap<>();
|
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);
|
bodyParameter.setSchema(bodySchema);
|
||||||
|
|
||||||
// 构建参数构建完成
|
// 构建参数构建完成
|
||||||
|
|
@ -351,7 +366,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||||
|
|
||||||
operation.setResponses(responses);
|
operation.setResponses(responses);
|
||||||
operations.put(openApi.getRequestMethod().toLowerCase(), operation);
|
operations.put(openApi.getRequestMethod().toLowerCase(), operation);
|
||||||
paths.put("/openapi/call/"+openApi.getRequestUrl(), operations);
|
paths.put("/openapi/call/" + openApi.getRequestUrl(), operations);
|
||||||
}
|
}
|
||||||
|
|
||||||
swaggerModel.setDefinitions(definitions);
|
swaggerModel.setDefinitions(definitions);
|
||||||
|
|
@ -361,7 +376,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||||
|
|
||||||
private void parameters(SwaggerOperation operation, OpenApi openApi) {
|
private void parameters(SwaggerOperation operation, OpenApi openApi) {
|
||||||
List<SwaggerOperationParameter> parameters = new ArrayList<>();
|
List<SwaggerOperationParameter> parameters = new ArrayList<>();
|
||||||
if (openApi.getParamsJson()!=null) {
|
if (openApi.getParamsJson() != null) {
|
||||||
List<OpenApiParam> openApiParams = JSON.parseArray(openApi.getParamsJson(), OpenApiParam.class);
|
List<OpenApiParam> openApiParams = JSON.parseArray(openApi.getParamsJson(), OpenApiParam.class);
|
||||||
for (OpenApiParam openApiParam : openApiParams) {
|
for (OpenApiParam openApiParam : openApiParams) {
|
||||||
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
||||||
|
|
@ -372,7 +387,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||||
parameters.add(parameter);
|
parameters.add(parameter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (openApi.getHeadersJson()!=null) {
|
if (openApi.getHeadersJson() != null) {
|
||||||
List<OpenApiHeader> openApiHeaders = JSON.parseArray(openApi.getHeadersJson(), OpenApiHeader.class);
|
List<OpenApiHeader> openApiHeaders = JSON.parseArray(openApi.getHeadersJson(), OpenApiHeader.class);
|
||||||
for (OpenApiHeader openApiHeader : openApiHeaders) {
|
for (OpenApiHeader openApiHeader : openApiHeaders) {
|
||||||
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
||||||
|
|
@ -410,6 +425,7 @@ public class OpenApiController extends JeecgController<OpenApi, OpenApiService>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成接口路径
|
* 生成接口路径
|
||||||
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@GetMapping("genPath")
|
@GetMapping("genPath")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue