From 981b83aa2d99a0daf344bcbc0b70a3c4fdbdfff4 Mon Sep 17 00:00:00 2001 From: "HUOJIN\\92525" <925259474@qq.com> Date: Sun, 14 Dec 2025 23:40:22 +0800 Subject: [PATCH] no message --- .../org/jeecg/common/api/vo}/HikResult.java | 9 +- .../org/jeecg/common/api/vo}/TesResult.java | 2 +- .../org/jeecg/common/aspect/DictAspect.java | 125 ++-- .../modules/agvTask/mapper/AgvTaskMapper.java | 12 +- .../service/ScanTrayProcessor.java | 398 +++++++++++ .../impl/IConveyorLineServiceImpl.java | 328 +-------- .../modules/conveyorLine/vo/ScanTrayData.java | 26 + .../hikAgv/controller/HikAgvController.java | 4 +- .../service/impl/IHikAgvServiceImpl.java | 3 +- .../inventory/mapper/InventoryMapper.java | 4 +- .../cpte/modules/quartz/job/AllocateJob.java | 8 +- .../cpte/modules/quartz/job/HikAgvJob.java | 4 +- .../receive/mapper/AsnDetailMapper.java | 2 +- .../modules/receive/mapper/AsnMapper.java | 2 +- .../receive/service/IAsnDetailService.java | 26 + .../modules/receive/service/IAsnService.java | 8 +- .../receive/service/ReceiveProcessor.java | 241 +++++++ .../service/impl/AsnDetailServiceImpl.java | 77 +- .../receive/service/impl/AsnServiceImpl.java | 213 ++---- .../cpte/modules/receive/vo/ReceiveData.java | 17 + .../saiWms/controller/SaiWmsController.java | 5 +- .../saiWms/request/OutboundRequest.java | 2 +- .../{SaiWmsRequest.java => SMOMRequest.java} | 2 +- ...{ISaiWmsService.java => ISMOMService.java} | 2 +- .../saiWms/service/InBoundTaskProcessor.java | 247 +++++++ .../saiWms/service/OutBoundTaskProcessor.java | 187 +++++ .../saiWms/service/SyncStockProcessor.java | 145 ++++ .../saiWms/service/impl/ISMOMServiceImpl.java | 42 ++ .../service/impl/ISaiWmsServiceImpl.java | 300 -------- .../shipping/controller/PickController.java | 11 + .../cpte/modules/shipping/entity/Task.java | 5 + .../modules/shipping/mapper/PickMapper.java | 2 +- .../modules/shipping/mapper/TaskMapper.java | 5 +- .../shipping/service/AllocateProcessor.java | 673 ++++++++++++++++++ .../service/CancelAllocateProcessor.java | 297 ++++++++ .../shipping/service/IPickDetailService.java | 20 + .../shipping/service/IPickService.java | 4 +- .../shipping/service/ITaskService.java | 3 +- .../service/impl/PickDetailServiceImpl.java | 97 ++- .../service/impl/PickServiceImpl.java | 580 ++------------- .../service/impl/TaskServiceImpl.java | 10 +- .../modules/shipping/vo/AllocationData.java | 28 + .../shipping/vo/CancelAllocateData.java | 19 + ...ventoryGroupKey.java => ItemGroupKey.java} | 6 +- .../tesAgv/controller/TesAgvController.java | 3 +- .../service/impl/ITesAgvServiceImpl.java | 33 +- .../org/cpte/modules/utils/BatchUtil.java | 28 +- .../cpte/modules/utils/CodeGeneratorUtil.java | 14 +- .../utils/RedisDistributedLockUtil.java | 57 ++ .../org/cpte/modules/utils/SwmsLoginUtil.java | 14 +- .../java/org/cpte/modules/utils/test.java | 8 +- .../openapi/controller/OpenApiController.java | 66 +- 52 files changed, 2934 insertions(+), 1490 deletions(-) rename {cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/hikAgv/response => cpte-boot-base-core/src/main/java/org/jeecg/common/api/vo}/HikResult.java (83%) rename {cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/tesAgv/response => cpte-boot-base-core/src/main/java/org/jeecg/common/api/vo}/TesResult.java (95%) create mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/service/ScanTrayProcessor.java create mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/vo/ScanTrayData.java create mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/ReceiveProcessor.java create mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/vo/ReceiveData.java rename cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/request/{SaiWmsRequest.java => SMOMRequest.java} (99%) rename cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/{ISaiWmsService.java => ISMOMService.java} (94%) create mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/InBoundTaskProcessor.java create mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/OutBoundTaskProcessor.java create mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/SyncStockProcessor.java create mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/impl/ISMOMServiceImpl.java delete mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/impl/ISaiWmsServiceImpl.java create mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/AllocateProcessor.java create mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/CancelAllocateProcessor.java create mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/AllocationData.java create mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/CancelAllocateData.java rename cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/{InventoryGroupKey.java => ItemGroupKey.java} (81%) create mode 100644 cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/RedisDistributedLockUtil.java diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/hikAgv/response/HikResult.java b/cpte-boot-base-core/src/main/java/org/jeecg/common/api/vo/HikResult.java similarity index 83% rename from cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/hikAgv/response/HikResult.java rename to cpte-boot-base-core/src/main/java/org/jeecg/common/api/vo/HikResult.java index 19defa0..2a08313 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/hikAgv/response/HikResult.java +++ b/cpte-boot-base-core/src/main/java/org/jeecg/common/api/vo/HikResult.java @@ -1,8 +1,6 @@ -package org.cpte.modules.hikAgv.response; +package org.jeecg.common.api.vo; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; @@ -14,11 +12,6 @@ import java.io.Serializable; * @email cpteos@163.com * @date 2019年1月19日 */ -@JsonPropertyOrder({ - "code", - "message", - "data" -}) @Data public class HikResult implements Serializable { diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/tesAgv/response/TesResult.java b/cpte-boot-base-core/src/main/java/org/jeecg/common/api/vo/TesResult.java similarity index 95% rename from cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/tesAgv/response/TesResult.java rename to cpte-boot-base-core/src/main/java/org/jeecg/common/api/vo/TesResult.java index 669ba1e..75f8568 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/tesAgv/response/TesResult.java +++ b/cpte-boot-base-core/src/main/java/org/jeecg/common/api/vo/TesResult.java @@ -1,4 +1,4 @@ -package org.cpte.modules.tesAgv.response; +package org.jeecg.common.api.vo; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/cpte-boot-base-core/src/main/java/org/jeecg/common/aspect/DictAspect.java b/cpte-boot-base-core/src/main/java/org/jeecg/common/aspect/DictAspect.java index 6756d2c..7076c8e 100644 --- a/cpte-boot-base-core/src/main/java/org/jeecg/common/aspect/DictAspect.java +++ b/cpte-boot-base-core/src/main/java/org/jeecg/common/aspect/DictAspect.java @@ -49,38 +49,25 @@ public class DictAspect { private static final String JAVA_UTIL_DATE = "java.util.Date"; - /** - * 定义切点Pointcut - */ -/* @Pointcut("(@within(org.springframework.web.bind.annotation.RestController) || " + - "@within(org.springframework.stereotype.Controller) || @annotation(org.jeecg.common.aspect.annotation.AutoDict)) " + - "&& execution(public org.jeecg.common.api.vo.Result org.jeecg..*.*(..)) || execution(public * org.cpte..*.*Controller.*(..))") - public void excudeService() { - }*/ - /** * 定义切点Pointcut */ @Pointcut("(@within(org.springframework.web.bind.annotation.RestController) || " + - "@within(org.springframework.stereotype.Controller) || " + - "@annotation(org.jeecg.common.aspect.annotation.AutoDict)) " + - "&& (execution(public org.jeecg.common.api.vo.Result org.jeecg..*.*(..)) || " + - "(execution(public * org.cpte..*.*Controller.*(..)) && " + - "!execution(public org.cpte.modules.hikAgv.response.* org.cpte.modules.hikAgv.controller.*.*(..)) && " + - "!execution(public org.cpte.modules.tesAgv.response.* org.cpte.modules.tesAgv.controller.*.*(..))))") + "@within(org.springframework.stereotype.Controller) || @annotation(org.jeecg.common.aspect.annotation.AutoDict)) " + + "&& execution(public org.jeecg.common.api.vo.Result org.jeecg..*.*(..))") public void excudeService() { } @Around("excudeService()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { - long time1=System.currentTimeMillis(); + long time1 = System.currentTimeMillis(); Object result = pjp.proceed(); - long time2=System.currentTimeMillis(); - log.debug("获取JSON数据 耗时:"+(time2-time1)+"ms"); - long start=System.currentTimeMillis(); - result=this.parseDictText(result); - long end=System.currentTimeMillis(); - log.debug("注入字典到JSON数据 耗时"+(end-start)+"ms"); + long time2 = System.currentTimeMillis(); + log.debug("获取JSON数据 耗时:" + (time2 - time1) + "ms"); + long start = System.currentTimeMillis(); + result = this.parseDictText(result); + long end = System.currentTimeMillis(); + log.debug("注入字典到JSON数据 耗时" + (end - start) + "ms"); return result; } @@ -90,20 +77,21 @@ public class DictAspect { * 示例为SysUser 字段为sex 添加了注解@Dict(dicCode = "sex") 会在字典服务立马查出来对应的text 然后在请求list的时候将这个字典text,已字段名称加_dictText形式返回到前端 * 例输入当前返回值的就会多出一个sex_dictText字段 * { - * sex:1, - * sex_dictText:"男" + * sex:1, + * sex_dictText:"男" * } * 前端直接取值sext_dictText在table里面无需再进行前端的字典转换了 - * customRender:function (text) { - * if(text==1){ - * return "男"; - * }else if(text==2){ - * return "女"; - * }else{ - * return text; - * } - * } - * 目前vue是这么进行字典渲染到table上的多了就很麻烦了 这个直接在服务端渲染完成前端可以直接用 + * customRender:function (text) { + * if(text==1){ + * return "男"; + * }else if(text==2){ + * return "女"; + * }else{ + * return text; + * } + * } + * 目前vue是这么进行字典渲染到table上的多了就很麻烦了 这个直接在服务端渲染完成前端可以直接用 + * * @param result */ private Object parseDictText(Object result) { @@ -117,24 +105,24 @@ public class DictAspect { // 字典数据列表, key = 字典code,value=数据列表 Map> dataListMap = new HashMap<>(5); //取出结果集 - List records=((IPage) ((Result) result).getResult()).getRecords(); + List records = ((IPage) ((Result) result).getResult()).getRecords(); //update-begin--Author:zyf -- Date:20220606 ----for:【VUEN-1230】 判断是否含有字典注解,没有注解返回----- - Boolean hasDict= checkHasDict(records); - if(!hasDict){ + Boolean hasDict = checkHasDict(records); + if (!hasDict) { return result; } - log.debug(" __ 进入字典翻译切面 DictAspect —— " ); + log.debug(" __ 进入字典翻译切面 DictAspect —— "); //update-end--Author:zyf -- Date:20220606 ----for:【VUEN-1230】 判断是否含有字典注解,没有注解返回----- for (Object record : records) { - String json="{}"; + String json = "{}"; try { //update-begin--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错----- //解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat - json = objectMapper.writeValueAsString(record); + json = objectMapper.writeValueAsString(record); //update-end--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错----- } catch (JsonProcessingException e) { - log.error("json解析失败"+e.getMessage(),e); + log.error("json解析失败" + e.getMessage(), e); } //update-begin--Author:scott -- Date:20211223 ----for:【issues/3303】restcontroller返回json数据后key顺序错乱 ----- JSONObject item = JSONObject.parseObject(json, Feature.OrderedField); @@ -148,7 +136,7 @@ public class DictAspect { if (oConvertUtils.isEmpty(value)) { continue; } - //update-end--Author:scott -- Date:20190603 ----for:解决继承实体字段无法翻译问题------ + //update-end--Author:scott -- Date:20190603 ----for:解决继承实体字段无法翻译问题------ if (field.getAnnotation(Dict.class) != null) { if (!dictFieldList.contains(field)) { dictFieldList.add(field); @@ -172,8 +160,8 @@ public class DictAspect { //date类型默认转换string格式化日期 //update-begin--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错----- //if (JAVA_UTIL_DATE.equals(field.getType().getName())&&field.getAnnotation(JsonFormat.class)==null&&item.get(field.getName())!=null){ - //SimpleDateFormat aDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - // item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName())))); + //SimpleDateFormat aDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + // item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName())))); //} //update-end--Author:zyf -- Date:20220531 ----for:【issues/#3629】 DictAspect Jackson序列化报错----- } @@ -203,7 +191,7 @@ public class DictAspect { String value = record.getString(field.getName()); if (oConvertUtils.isNotEmpty(value)) { List dictModels = translText.get(fieldDictCode); - if(dictModels==null || dictModels.size()==0){ + if (dictModels == null || dictModels.size() == 0) { continue; } @@ -242,6 +230,7 @@ public class DictAspect { * 一次性把所有的字典都翻译了 * 1. 所有的普通数据字典的所有数据只执行一次SQL * 2. 表字典相同的所有数据只执行一次SQL + * * @param dataListMap * @return */ @@ -309,13 +298,13 @@ public class DictAspect { log.debug("translateDictFromTableByKeys.dictCode:" + dictCode); log.debug("translateDictFromTableByKeys.values:" + values); //update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------ - + //update-begin---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串--- - if(null == dataSource){ + if (null == dataSource) { dataSource = ""; } //update-end---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串--- - + List texts = commonApi.translateDictFromTableByKeys(table, text, code, values, dataSource); //update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------ log.debug("translateDictFromTableByKeys.result:" + texts); @@ -393,7 +382,8 @@ public class DictAspect { } /** - * 翻译字典文本 + * 翻译字典文本 + * * @param code * @param text * @param table @@ -402,39 +392,39 @@ public class DictAspect { */ @Deprecated private String translateDictValue(String code, String text, String table, String key) { - if(oConvertUtils.isEmpty(key)) { - return null; - } - StringBuffer textValue=new StringBuffer(); + if (oConvertUtils.isEmpty(key)) { + return null; + } + StringBuffer textValue = new StringBuffer(); String[] keys = key.split(","); for (String k : keys) { String tmpValue = null; - log.debug(" 字典 key : "+ k); + log.debug(" 字典 key : " + k); if (k.trim().length() == 0) { continue; //跳过循环 } //update-begin--Author:scott -- Date:20210531 ----for: !56 优化微服务应用下存在表字段需要字典翻译时加载缓慢问题----- - if (!StringUtils.isEmpty(table)){ - log.debug("--DictAspect------dicTable="+ table+" ,dicText= "+text+" ,dicCode="+code); - String keyString = String.format("sys:cache:dictTable::SimpleKey [%s,%s,%s,%s]",table,text,code,k.trim()); - if (redisTemplate.hasKey(keyString)){ + if (!StringUtils.isEmpty(table)) { + log.debug("--DictAspect------dicTable=" + table + " ,dicText= " + text + " ,dicCode=" + code); + String keyString = String.format("sys:cache:dictTable::SimpleKey [%s,%s,%s,%s]", table, text, code, k.trim()); + if (redisTemplate.hasKey(keyString)) { try { tmpValue = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString)); } catch (Exception e) { log.warn(e.getMessage()); } - }else { - tmpValue= commonApi.translateDictFromTable(table,text,code,k.trim()); + } else { + tmpValue = commonApi.translateDictFromTable(table, text, code, k.trim()); } - }else { - String keyString = String.format("sys:cache:dict::%s:%s",code,k.trim()); - if (redisTemplate.hasKey(keyString)){ + } else { + String keyString = String.format("sys:cache:dict::%s:%s", code, k.trim()); + if (redisTemplate.hasKey(keyString)) { try { tmpValue = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString)); } catch (Exception e) { - log.warn(e.getMessage()); + log.warn(e.getMessage()); } - }else { + } else { tmpValue = commonApi.translateDict(code, k.trim()); } } @@ -453,11 +443,12 @@ public class DictAspect { /** * 检测返回结果集中是否包含Dict注解 + * * @param records * @return */ - private Boolean checkHasDict(List records){ - if(oConvertUtils.isNotEmpty(records) && records.size()>0){ + private Boolean checkHasDict(List records) { + if (oConvertUtils.isNotEmpty(records) && records.size() > 0) { for (Field field : oConvertUtils.getAllFields(records.get(0))) { if (oConvertUtils.isNotEmpty(field.getAnnotation(Dict.class))) { return true; diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/agvTask/mapper/AgvTaskMapper.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/agvTask/mapper/AgvTaskMapper.java index 65e2833..bb4d3e6 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/agvTask/mapper/AgvTaskMapper.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/agvTask/mapper/AgvTaskMapper.java @@ -20,8 +20,8 @@ public interface AgvTaskMapper extends BaseMapper { * @param carrierCode 容器 * @param agvVendor 供应商;AGV/TES */ - @Select(value = "select count(id) from data_agv_task where carrier_code = #{carrierCode} and status in (1,2,3) and agv_vendor = #{agvVendor} for update ") - Long existsByStockCode(@Param("carrierCode") String carrierCode, @Param("agvVendor") String agvVendor); + @Select(value = "select 1 from data_agv_task where carrier_code = #{carrierCode} and status in (1,2,3) and agv_vendor = #{agvVendor} LIMIT 1 ") + Integer existsByStockCode(@Param("carrierCode") String carrierCode, @Param("agvVendor") String agvVendor); /** * 根据起点编码验证AGV任务是否存在 @@ -29,8 +29,8 @@ public interface AgvTaskMapper extends BaseMapper { * @param startCode 起点 * @param agvVendor 供应商;AGV/TES */ - @Select(value = "select count(id) from data_agv_task where start_code = #{startCode} and status in (2,3) and agv_vendor = #{agvVendor} for update ") - Long existsByStartCode(@Param("startCode") String startCode, @Param("agvVendor") String agvVendor); + @Select(value = "select 1 from data_agv_task where start_code = #{startCode} and status in (2,3) and agv_vendor = #{agvVendor} LIMIT 1 ") + Integer existsByStartCode(@Param("startCode") String startCode, @Param("agvVendor") String agvVendor); /** @@ -39,8 +39,8 @@ public interface AgvTaskMapper extends BaseMapper { * @param endCode 终点 * @param agvVendor 供应商;AGV/TES */ - @Select(value = "select count(id) from data_agv_task where end_code = #{endCode} and status in (2,3) and agv_vendor = #{agvVendor} for update ") - Long existsByEndCode(@Param("endCode") String endCode, @Param("agvVendor") String agvVendor); + @Select(value = "select 1 from data_agv_task where end_code = #{endCode} and status in (2,3) and agv_vendor = #{agvVendor} LIMIT 1 ") + Integer existsByEndCode(@Param("endCode") String endCode, @Param("agvVendor") String agvVendor); /** * 查询AGV任务列表 diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/service/ScanTrayProcessor.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/service/ScanTrayProcessor.java new file mode 100644 index 0000000..4059a17 --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/service/ScanTrayProcessor.java @@ -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 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 projectList = asnDetails.stream().map(AsnDetail::getProject).filter(StringUtils::isNotBlank).distinct().toList(); + List taskNoList = asnDetails.stream().map(AsnDetail::getTaskNo).filter(StringUtils::isNotBlank).distinct().toList(); + List propC1List = asnDetails.stream().map(AsnDetail::getPropC1).filter(StringUtils::isNotBlank).distinct().toList(); + List propC3List = asnDetails.stream().map(AsnDetail::getPropC3).filter(StringUtils::isNotBlank).distinct().toList(); + + List 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 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 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 itemKeyIds = itemKeys.stream().map(ItemKey::getId).toList(); + List availablePoints = pointService.findClusterPoint(itemKeyIds, areaCode); + if (CollectionUtils.isEmpty(availablePoints)) { + //2.获取所有可用库位 + availablePoints = pointMapper.queryPoints(null, CommonStatusEnum.FREE.getValue(), areaCode); + } + + List 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 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 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 neighbors = inventoryMapper.findNeighborPoints( + point.getColNum(), point.getLayerNum(), String.valueOf(minDepth), String.valueOf(maxDepth)); + if (CollectionUtils.isEmpty(neighbors)) { + return score; + } + List itemKeyIds = neighbors.stream().map(Inventory::getItemKeyId).distinct().toList(); + List 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()); + } + +} diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/service/impl/IConveyorLineServiceImpl.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/service/impl/IConveyorLineServiceImpl.java index 7fe9587..4197038 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/service/impl/IConveyorLineServiceImpl.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/service/impl/IConveyorLineServiceImpl.java @@ -6,51 +6,22 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.cpte.modules.agvTask.entity.AgvTask; import org.cpte.modules.agvTask.mapper.AgvTaskMapper; -import org.cpte.modules.agvTask.service.IAgvTaskService; -import org.cpte.modules.base.entity.ItemKey; -import org.cpte.modules.base.entity.Point; -import org.cpte.modules.base.entity.Stock; -import org.cpte.modules.base.mapper.ItemKeyMapper; -import org.cpte.modules.base.mapper.PointMapper; -import org.cpte.modules.base.mapper.StockMapper; -import org.cpte.modules.base.service.IPointService; import org.cpte.modules.constant.enums.*; import org.cpte.modules.conveyorLine.request.ScanTrayRequest; import org.cpte.modules.conveyorLine.service.IConveyorLineService; -import org.cpte.modules.conveyorLine.vo.PointScore; -import org.cpte.modules.inventory.entity.Inventory; -import org.cpte.modules.inventory.mapper.InventoryMapper; -import org.cpte.modules.receive.entity.Asn; -import org.cpte.modules.receive.entity.AsnDetail; -import org.cpte.modules.receive.mapper.AsnDetailMapper; -import org.cpte.modules.receive.mapper.AsnMapper; +import org.cpte.modules.conveyorLine.service.ScanTrayProcessor; import org.cpte.modules.shipping.entity.Task; import org.cpte.modules.shipping.mapper.TaskMapper; +import org.cpte.modules.utils.RedisDistributedLockUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import java.util.Collections; -import java.util.Comparator; import java.util.List; -import java.util.Set; @Service @Slf4j public class IConveyorLineServiceImpl implements IConveyorLineService { - @Autowired - private PointMapper pointMapper; - - @Autowired - private StockMapper stockMapper; - - @Autowired - private AsnMapper asnMapper; - - @Autowired - private AsnDetailMapper asnDetailMapper; - @Autowired private TaskMapper taskMapper; @@ -58,296 +29,27 @@ public class IConveyorLineServiceImpl implements IConveyorLineService { private AgvTaskMapper agvTaskMapper; @Autowired - private ItemKeyMapper itemKeyMapper; + private ScanTrayProcessor scanTrayProcessor; @Autowired - private InventoryMapper inventoryMapper; - - @Autowired - private IPointService pointService; - - @Autowired - private IAgvTaskService iAgvTaskService; - - private void validateParams(ScanTrayRequest scanTrayRequest) { - if (StringUtils.isBlank(scanTrayRequest.getStockCode())) { - throw new RuntimeException("托盘码不能为空"); - } - if (StringUtils.isBlank(scanTrayRequest.getStation())) { - throw new RuntimeException("工作站不能为空"); - } - } + private RedisDistributedLockUtil redissonLock; @Override - @Transactional(rollbackFor = Exception.class) public void scanTray(ScanTrayRequest scanTrayRequest) { - //验证参数 - validateParams(scanTrayRequest); - - //工作站 - Point station = pointMapper.queryByPointCode(scanTrayRequest.getStation()); - - //容器 - Stock stock = stockMapper.queryByStockCode(scanTrayRequest.getStockCode()); - - //验证入库信息 - List asnDetails = asnDetailMapper.queryByStockCode(stock.getId(), AsnStatusEnum.CREATED.getValue()); - if (CollectionUtils.isEmpty(asnDetails)) { - throw new RuntimeException("【" + scanTrayRequest.getStockCode() + "】托盘,无入库任务"); - } - Asn asn = asnMapper.selectById(asnDetails.get(0).getAsnId()); - AsnDetail asnDetail = asnDetails.get(0); - //验证托盘是否有库存 - if (inventoryMapper.queryByStockId(stock.getId()) != null) { - throw new RuntimeException("【" + scanTrayRequest.getStockCode() + "】托盘已入库"); - } - - //验证当前托盘是否生成了TES任务 - if (agvTaskMapper.existsByStockCode(stock.getStockCode(), AgvVendorEnum.TES.getValue()) > 0) { - throw new RuntimeException("【" + scanTrayRequest.getStockCode() + "】托盘已扫描,请勿重复扫描"); - } - - //项目号、任务号、批次号、外部库存状态 - List projectList = asnDetails.stream().map(AsnDetail::getProject).filter(StringUtils::isNotBlank).distinct().toList(); - List taskNoList = asnDetails.stream().map(AsnDetail::getTaskNo).filter(StringUtils::isNotBlank).distinct().toList(); - List propC1List = asnDetails.stream().map(AsnDetail::getPropC1).filter(StringUtils::isNotBlank).distinct().toList(); - List propC3List = asnDetails.stream().map(AsnDetail::getPropC3).filter(StringUtils::isNotBlank).distinct().toList(); - - List 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 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 itemKeyIds = itemKeys.stream().map(ItemKey::getId).toList(); - List availablePoints = pointService.findClusterPoint(itemKeyIds, areaCode); - if (CollectionUtils.isEmpty(availablePoints)) { - //2.获取所有可用库位 - availablePoints = pointMapper.queryPoints(null, CommonStatusEnum.FREE.getValue(), areaCode); - } - - List 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 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 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 neighbors = inventoryMapper.findNeighborPoints( - point.getColNum(), point.getLayerNum(), String.valueOf(minDepth), String.valueOf(maxDepth)); - if (CollectionUtils.isEmpty(neighbors)) { - return score; - } - List itemKeyIds = neighbors.stream().map(Inventory::getItemKeyId).distinct().toList(); - List exitItemKeys = itemKeyMapper.queryItemKeyByIds(itemKeyIds); - - // 为每个ItemKey计算匹配度并累加分数 - for (ItemKey itemKey : itemKeys) { - Long itemId = itemKey.getItemId(); - String whCode = itemKey.getWhCode(); - String project = itemKey.getProject(); - String taskNo = itemKey.getTaskNo(); - String propC1 = itemKey.getPropC1(); - String propC3 = itemKey.getPropC3(); - - for (ItemKey neighbor : exitItemKeys) { - // 同SKU加分 - if (neighbor.getItemId().equals(itemId)) { - score += 20; - - // 同仓库代码加分 - if (whCode != null && whCode.equals(neighbor.getWhCode())) { - score += 15; - } - - // 同批次号加分 - if (StringUtils.isNotBlank(propC1) && - StringUtils.isNotBlank(neighbor.getPropC1()) && - propC1.equals(neighbor.getPropC1())) { - score += 10; - } - - // 同项目号加分 - if (StringUtils.isNotBlank(project) && - StringUtils.isNotBlank(neighbor.getProject()) && - project.equals(neighbor.getProject())) { - score += 10; - } - - // 同任务号加分 - if (StringUtils.isNotBlank(taskNo) && - StringUtils.isNotBlank(neighbor.getTaskNo()) && - taskNo.equals(neighbor.getTaskNo())) { - score += 10; - } - - // 同外部库存状态加分 - if (StringUtils.isNotBlank(propC3) && - StringUtils.isNotBlank(neighbor.getPropC3()) && - propC3.equals(neighbor.getPropC3())) { - score += 5; - } - } + String lockKey = "scanTray:" + scanTrayRequest.getStockCode(); + String lockValue = null; + try { + lockValue = redissonLock.tryLock(lockKey, 10); + if (StringUtils.isEmpty(lockValue)) { + throw new RuntimeException("扫描处理中,请稍后重试"); + } + scanTrayProcessor.scanTray(scanTrayRequest); + } finally { + if (StringUtils.isNotEmpty(lockValue)) { + redissonLock.unlock(lockKey, lockValue); } } - // 限制最高分 - return Math.min(score, 100); - } - - - /** - * 计算距离评分 - * 基于入库口位置和库位坐标计算最短路径距离 - */ - private double calculateClusterDistanceCost(Point point, Point station) { - // 计算曼哈顿距离 - double distance = Math.abs(point.getPositionX() - station.getPositionX()) + Math.abs(point.getPositionY() - station.getPositionY()); - - // 距离越小分数越高 - return Math.max(0, 100 - (distance / 100.0)); } @Override diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/vo/ScanTrayData.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/vo/ScanTrayData.java new file mode 100644 index 0000000..41d584f --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/conveyorLine/vo/ScanTrayData.java @@ -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 asnDetails; + private List itemKeys; +} diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/hikAgv/controller/HikAgvController.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/hikAgv/controller/HikAgvController.java index f7fb6a8..8377e72 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/hikAgv/controller/HikAgvController.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/hikAgv/controller/HikAgvController.java @@ -10,9 +10,7 @@ import org.cpte.modules.hikAgv.request.CancelRequest; import org.cpte.modules.hikAgv.request.SubmitRequest; import org.cpte.modules.hikAgv.request.TaskReporterRequest; import org.cpte.modules.hikAgv.service.IHikAgvService; -import org.cpte.modules.tesAgv.response.TesResult; -import org.jeecg.common.api.vo.Result; -import org.cpte.modules.hikAgv.response.HikResult; +import org.jeecg.common.api.vo.HikResult; import org.jeecg.common.aspect.annotation.AutoLog; import org.jeecg.config.shiro.IgnoreAuth; import org.springframework.beans.factory.annotation.Autowired; diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/hikAgv/service/impl/IHikAgvServiceImpl.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/hikAgv/service/impl/IHikAgvServiceImpl.java index 9ddc49a..7184b5b 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/hikAgv/service/impl/IHikAgvServiceImpl.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/hikAgv/service/impl/IHikAgvServiceImpl.java @@ -240,8 +240,7 @@ public class IHikAgvServiceImpl implements IHikAgvService { * @param agvTask 任务 */ private void handleResend(AgvTask agvTask) { - Long count = agvTaskMapper.existsByStockCode(agvTask.getCarrierCode(), AgvVendorEnum.HIK.getValue()); - if (count > 0) { + if (agvTaskMapper.existsByStockCode(agvTask.getCarrierCode(), AgvVendorEnum.HIK.getValue()) !=null) { throw new RuntimeException("任务已重新生成,请勿重复操作! "); } AgvTask newAgvTask = iAgvTaskService.createAgvTask(agvTask.getBusinessDetailId(), agvTask.getCarrierCode(), agvTask.getStartCode(), agvTask.getEndCode(), null, agvTask.getType(),agvTask.getIzAll(), AgvVendorEnum.HIK.getValue()); diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/mapper/InventoryMapper.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/mapper/InventoryMapper.java index bff2e09..cb9d244 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/mapper/InventoryMapper.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/mapper/InventoryMapper.java @@ -23,8 +23,8 @@ public interface InventoryMapper extends BaseMapper { * @param stockId 容器 * @return 库存数据 */ - @Select("select * from data_inventory where stock_id = #{stockId} and quantity>0 for update") - Inventory queryByStockId(@Param("stockId") Long stockId); + @Select("select 1 from data_inventory where stock_id = #{stockId} and quantity>0 LIMIT 1 ") + Integer exitsStockInventory(@Param("stockId") Long stockId); // 查询相邻库位(同一巷道,深度±3范围内) @Select("SELECT di.* " + diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/quartz/job/AllocateJob.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/quartz/job/AllocateJob.java index b1bb3a9..7029674 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/quartz/job/AllocateJob.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/quartz/job/AllocateJob.java @@ -3,7 +3,6 @@ package org.cpte.modules.quartz.job; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.cpte.modules.shipping.mapper.PickMapper; -import org.cpte.modules.shipping.mapper.TaskMapper; import org.cpte.modules.shipping.service.IPickService; import org.cpte.modules.shipping.service.ITaskService; import org.jeecg.common.constant.CommonConstant; @@ -28,10 +27,7 @@ public class AllocateJob implements Job { private PickMapper pickMapper; @Autowired - private TaskMapper taskMapper; - - @Autowired - private IPickService iPickService; + private IPickService pickService; @Autowired private ITaskService iTaskService; @@ -52,7 +48,7 @@ public class AllocateJob implements Job { if (CollectionUtils.isNotEmpty(pickList)) { // 分配出库 long startTime = System.currentTimeMillis(); - List resultMsg = iPickService.allocatePick(pickList); + List resultMsg = pickService.allocatePick(pickList); long endTime = System.currentTimeMillis(); log.info("分配出库明细耗时:{}ms", endTime - startTime); if (CollectionUtils.isNotEmpty(resultMsg)) { diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/quartz/job/HikAgvJob.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/quartz/job/HikAgvJob.java index 1b63c29..1ed43ab 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/quartz/job/HikAgvJob.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/quartz/job/HikAgvJob.java @@ -32,7 +32,7 @@ public class HikAgvJob implements Job { String taskSubmitUrl = "http://localhost:8000/cpte-wms/rcs/rtas/api/robot/controller/task/submit"; for (AgvTask agvTask : agvTaskList) { if(BusinessTypeEnum.INBOUND.getValue().equals(agvTask.getType())){ - boolean isStartCodeAvailable = agvTaskMapper.existsByStartCode(agvTask.getStartCode(), AgvVendorEnum.HIK.getValue()) == 0; + boolean isStartCodeAvailable = agvTaskMapper.existsByStartCode(agvTask.getStartCode(), AgvVendorEnum.HIK.getValue()) ==null ; if (isStartCodeAvailable) { hikAgvService.sendHikAgvTask( taskSubmitUrl, @@ -41,7 +41,7 @@ public class HikAgvJob implements Job { ); } }else if(BusinessTypeEnum.OUTBOUND.getValue().equals(agvTask.getType())){ - boolean isEndCodeAvailable = agvTaskMapper.existsByEndCode(agvTask.getEndCode(), AgvVendorEnum.HIK.getValue()) == 0; + boolean isEndCodeAvailable = agvTaskMapper.existsByEndCode(agvTask.getEndCode(), AgvVendorEnum.HIK.getValue()) == null; if (isEndCodeAvailable) { hikAgvService.sendHikAgvTask( taskSubmitUrl, diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/mapper/AsnDetailMapper.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/mapper/AsnDetailMapper.java index b789d68..371ff66 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/mapper/AsnDetailMapper.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/mapper/AsnDetailMapper.java @@ -40,7 +40,7 @@ public interface AsnDetailMapper extends BaseMapper { * @param status 状态 * @return AsnDetail */ - @Select("select * from data_asn_detail where stock_id = #{stockId} and status = #{status} for update") + @Select("select * from data_asn_detail where stock_id = #{stockId} and status = #{status} ") List queryByStockCode(@Param("stockId") Long stockId, @Param("status") Integer status); @Select("select MAX(line_no) from data_asn_detail where asn_id = #{asnId} ") diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/mapper/AsnMapper.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/mapper/AsnMapper.java index 31675ad..d84d8ee 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/mapper/AsnMapper.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/mapper/AsnMapper.java @@ -17,7 +17,7 @@ public interface AsnMapper extends BaseMapper { * @param no 任务号 * @return Asn */ - @Select("select * from data_asn where no = #{no} for update ") + @Select("select * from data_asn where no = #{no} ") Asn queryByNo(@Param("no") String no); diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/IAsnDetailService.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/IAsnDetailService.java index 18ac554..ce8a69b 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/IAsnDetailService.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/IAsnDetailService.java @@ -1,13 +1,18 @@ package org.cpte.modules.receive.service; import org.cpte.modules.base.entity.Item; +import org.cpte.modules.base.entity.ItemKey; import org.cpte.modules.base.entity.Point; import org.cpte.modules.base.entity.Stock; +import org.cpte.modules.receive.entity.Asn; import org.cpte.modules.receive.entity.AsnDetail; import com.baomidou.mybatisplus.extension.service.IService; +import org.cpte.modules.receive.entity.ReceiveRecord; import org.cpte.modules.saiWms.request.InboundRequest; +import java.math.BigDecimal; import java.util.List; +import java.util.Map; /** * @Description: 入库明细 @@ -24,4 +29,25 @@ public interface IAsnDetailService extends IService { * @return List */ public List selectByMainId(Long mainId); + + /** + * 刷新入库单 + * + * @param asn 入库单 + * @param asnDetails 入库单明细 + */ + void refreshAsn(Asn asn, List asnDetails); + + /** + * 构建入库记录 + * + * @param asnDetail 入库单明细 + * @param receivedQty 收货数量 + * @param itemKey 物料属性 + * @param dstPointId 目标库位ID + * @return ReceiveRecord + */ + ReceiveRecord buildReceiveRecord(AsnDetail asnDetail, BigDecimal receivedQty, ItemKey itemKey, Long dstPointId); + + } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/IAsnService.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/IAsnService.java index 917e922..48d66a7 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/IAsnService.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/IAsnService.java @@ -1,14 +1,17 @@ package org.cpte.modules.receive.service; import org.cpte.modules.base.entity.Item; +import org.cpte.modules.base.entity.ItemKey; import org.cpte.modules.base.entity.Point; import org.cpte.modules.base.entity.Stock; import org.cpte.modules.receive.entity.AsnDetail; import org.cpte.modules.receive.entity.Asn; import com.baomidou.mybatisplus.extension.service.IService; +import org.cpte.modules.receive.entity.ReceiveRecord; import org.cpte.modules.saiWms.request.InboundRequest; import java.io.Serializable; +import java.math.BigDecimal; import java.util.Collection; import java.util.List; import java.util.Map; @@ -59,7 +62,7 @@ public interface IAsnService extends IService { */ Asn buildAsn(InboundRequest inboundRequest); - /** + /** * 构建入库单明细 * * @param inboundDetails 入库单明细 @@ -71,14 +74,13 @@ public interface IAsnService extends IService { */ List buildAsnDetail(List inboundDetails, Map itemMap, Stock stock, Point srcPoint, Point dstPoint); - /** * 收货操作 * * @param asnId 入库单ID * @param pointCode 目标库位 */ - void receiveGoods(Long asnId, String pointCode); + void receiveAsn(Long asnId, String pointCode); /** * 入库任务回传 diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/ReceiveProcessor.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/ReceiveProcessor.java new file mode 100644 index 0000000..2c15325 --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/ReceiveProcessor.java @@ -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 updateToAsnDetail = new ArrayList<>(); + List createToReceiveRecord = new ArrayList<>(); + List createToInventory = new ArrayList<>(); + List 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 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 updateToAsnDetail, + List createToReceiveRecord, + List createToInventory, + List 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 updateToAsnDetail,List createToReceiveRecord , List createToInventory, List 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); + } +} diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/impl/AsnDetailServiceImpl.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/impl/AsnDetailServiceImpl.java index d73c8a8..bb4ae87 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/impl/AsnDetailServiceImpl.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/impl/AsnDetailServiceImpl.java @@ -1,31 +1,98 @@ package org.cpte.modules.receive.service.impl; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; import org.cpte.modules.base.entity.Item; +import org.cpte.modules.base.entity.ItemKey; import org.cpte.modules.base.entity.Point; import org.cpte.modules.base.entity.Stock; import org.cpte.modules.constant.enums.AsnStatusEnum; +import org.cpte.modules.receive.entity.Asn; import org.cpte.modules.receive.entity.AsnDetail; +import org.cpte.modules.receive.entity.ReceiveRecord; import org.cpte.modules.receive.mapper.AsnDetailMapper; +import org.cpte.modules.receive.mapper.AsnMapper; import org.cpte.modules.receive.service.IAsnDetailService; import org.cpte.modules.saiWms.request.InboundRequest; +import org.cpte.modules.shipping.mapper.PickMapper; import org.springframework.stereotype.Service; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; import java.util.List; +import java.util.Map; + import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired; /** * @Description: 入库明细 * @author: cpte - * @Date: 2025-11-03 + * @Date: 2025-11-03 * @Version: V1.0 */ @Service public class AsnDetailServiceImpl extends ServiceImpl implements IAsnDetailService { - @Override - public List selectByMainId(Long mainId) { - return this.baseMapper.selectByMainId(mainId); - } + @Autowired + private AsnMapper asnMapper; + + @Override + public List selectByMainId(Long mainId) { + return this.baseMapper.selectByMainId(mainId); + } + + public void refreshAsn(Asn asn, List 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(); + } } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/impl/AsnServiceImpl.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/impl/AsnServiceImpl.java index 6f82722..8bd4ad1 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/impl/AsnServiceImpl.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/service/impl/AsnServiceImpl.java @@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.shiro.SecurityUtils; import org.cpte.modules.base.entity.Item; import org.cpte.modules.base.entity.ItemKey; @@ -29,12 +30,15 @@ import org.cpte.modules.receive.entity.AsnDetail; import org.cpte.modules.receive.entity.ReceiveRecord; import org.cpte.modules.receive.mapper.AsnDetailMapper; import org.cpte.modules.receive.mapper.AsnMapper; +import org.cpte.modules.receive.service.IAsnDetailService; import org.cpte.modules.receive.service.IAsnService; +import org.cpte.modules.receive.service.ReceiveProcessor; import org.cpte.modules.saiWms.request.InboundRequest; -import org.cpte.modules.saiWms.request.SaiWmsRequest; +import org.cpte.modules.saiWms.request.SMOMRequest; import org.cpte.modules.serialNumber.AsnSerialNumberRule; import org.cpte.modules.utils.BatchUtil; import org.cpte.modules.utils.BigDecimalUtil; +import org.cpte.modules.utils.RedisDistributedLockUtil; import org.cpte.modules.utils.SwmsLoginUtil; import org.jeecg.common.system.vo.LoginUser; import org.jeecg.modules.openapi.mapper.OpenApiMapper; @@ -60,41 +64,54 @@ import java.util.concurrent.atomic.AtomicInteger; @Slf4j public class AsnServiceImpl extends ServiceImpl implements IAsnService { - @Autowired - private StockMapper stockMapper; - @Autowired - private PointMapper pointMapper; + @Autowired private AsnDetailMapper asnDetailMapper; @Autowired - private InventoryMapper inventoryMapper; - @Autowired private OpenApiMapper openApiMapper; @Autowired private SysDictMapper sysDictMapper; @Autowired - private IStockService iStockService; + private IAsnDetailService asnDetailService; @Autowired - private IPointService iPointService; - @Autowired - private IItemKeyService iItemKeyService; - @Autowired - private IInventoryService iInventoryService; - @Autowired - private IInventoryLogService iInventoryLogService; + private ReceiveProcessor receiveProcessor; @Autowired private SwmsLoginUtil swmsLoginUtil; @Autowired - private BatchUtil batchUtil; - @Autowired private AsnSerialNumberRule asnSerialNumberRule; + @Autowired + private RedisDistributedLockUtil redissonLock; @Override - @Transactional(rollbackFor = Exception.class) public void saveMain(Asn asn, List 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 asnDetailList) { LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal(); asn.setTenantId(Long.parseLong(sysUser.getRelTenantIds())); asn.setSysOrgCode(sysUser.getOrgCode()); + String orderNo = asnSerialNumberRule.generateSerialNumber(GeneralConstant.ASN_ORDER_NO); + asn.setOrderNo(orderNo); this.baseMapper.insert(asn); if (asnDetailList == null || asnDetailList.isEmpty()) { @@ -112,43 +129,9 @@ public class AsnServiceImpl extends ServiceImpl implements IAsnS asnDetailMapper.insert(entity); } //刷新入库单 - refreshAsn(asn, asnDetailList); + asnDetailService.refreshAsn(asn, asnDetailList); } - public synchronized void refreshAsn(Asn asn, List asnDetails) { - - if (asnDetails == null) { - asnDetails = new ArrayList<>(); - } - - //需求数量 - BigDecimal orderQty = asnDetails.stream().map(AsnDetail::getOrderQty).reduce(BigDecimal.ZERO, BigDecimal::add); - - //收货数量 - BigDecimal receivedQty = asnDetails.stream().map(AsnDetail::getReceivedQty).reduce(BigDecimal.ZERO, BigDecimal::add); - - //当前状态 - Integer status = asn.getStatus(); - - if (orderQty.compareTo(BigDecimal.ZERO) <= 0) { - // 无需求量时设为创建状态 - status = AsnStatusEnum.CREATED.getValue(); - } else if (receivedQty.compareTo(BigDecimal.ZERO) <= 0) { - // 未收货时为创建状态 - status = AsnStatusEnum.CREATED.getValue(); - } else if (receivedQty.compareTo(orderQty) >= 0) { - // 已完全收货 - status = AsnStatusEnum.RECEIVED.getValue(); - } else { - // 部分收货 - status = AsnStatusEnum.RECEIVING.getValue(); - } - - asn.setOrderQty(orderQty); - asn.setReceivedQty(receivedQty); - asn.setStatus(status); - this.baseMapper.updateById(asn); - } @Override @Transactional(rollbackFor = Exception.class) @@ -176,7 +159,7 @@ public class AsnServiceImpl extends ServiceImpl implements IAsnS } } // 刷新入库单状态 - refreshAsn(asn, asnDetailList); + asnDetailService.refreshAsn(asn, asnDetailList); } @@ -211,9 +194,7 @@ public class AsnServiceImpl extends ServiceImpl implements IAsnS @Override public Asn buildAsn(InboundRequest inboundRequest) { - String orderNo = asnSerialNumberRule.generateSerialNumber(GeneralConstant.ASN_ORDER_NO); return Asn.builder() - .orderNo(orderNo) .thirdOrderNo(inboundRequest.getOrderNo()) .no(inboundRequest.getNo()) .whCode(inboundRequest.getWhCode()) @@ -236,7 +217,7 @@ public class AsnServiceImpl extends ServiceImpl implements IAsnS .receivedQty(BigDecimal.ZERO) .stockId(stock.getId()) .fromPointId(srcPoint == null ? null : srcPoint.getId()) - .toPointId(dstPoint.getId()) + .toPointId(dstPoint == null ? null : dstPoint.getId()) .status(AsnStatusEnum.CREATED.getValue()) .project(detail.getProject()) .taskNo(detail.getTaskNo()) @@ -249,80 +230,24 @@ public class AsnServiceImpl extends ServiceImpl implements IAsnS } + @Override - @Transactional(rollbackFor = Exception.class) - public void receiveGoods(Long asnId, String pointCode) { - //入库任务 - Asn asn = this.baseMapper.selectById(asnId); - if (asn == null) { - throw new RuntimeException("未匹配到入库任务【" + asnId + "】"); - } - List asnDetails = asnDetailMapper.selectByMainId(asnId); - //验证容器是否入库 - Stock stock = stockMapper.selectById(asnDetails.get(0).getStockId()); - if (inventoryMapper.queryByStockId(stock.getId()) != null) { - throw new RuntimeException("【" + stock.getStockCode() + "】托盘已入库"); - } - - //入库实际库位 - Point dstPoint = pointMapper.queryByPointCode(pointCode); - - //批量操作 - List updateToAsnDetail = new ArrayList<>(); - List createToReceiveRecord = new ArrayList<>(); - List createToInventory = new ArrayList<>(); - List createToInventoryLog = new ArrayList<>(); - - //收货逻辑 - for (AsnDetail asnDetail : asnDetails) { - BigDecimal receivedQty = BigDecimalUtil.add(asnDetail.getReceivedQty(), asnDetail.getOrderQty(), 0); - asnDetail.setReceivedQty(receivedQty); - //更新明细状态 - if (receivedQty.compareTo(asnDetail.getOrderQty()) >= 0) { - asnDetail.setStatus(AsnStatusEnum.RECEIVED.getValue()); - } else { - asnDetail.setStatus(AsnStatusEnum.RECEIVING.getValue()); + public void receiveAsn(Long asnId, String pointCode) { + String lockKey = "asn:" + asnId; + String lockValue = null; + try { + lockValue = redissonLock.tryLock(lockKey, 10); + if (StringUtils.isEmpty(lockValue)) { + throw new RuntimeException("收货处理中,请稍后重试"); + } + receiveProcessor.receiveAsn(asnId, pointCode); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + if (StringUtils.isNotEmpty(lockValue)) { + redissonLock.unlock(lockKey, lockValue); } - updateToAsnDetail.add(asnDetail); - - //获取itemKey - ItemKey itemKey = iItemKeyService.createItemKey(asnDetail.getItemId(), asn.getWhCode(), asnDetail.getProject(), asnDetail.getTaskNo(), asnDetail.getPropC1(), asnDetail.getPropC3()); - - //生成入库记录 - ReceiveRecord receiveRecord = buildReceiveRecord(asnDetail, receivedQty, itemKey, dstPoint.getId()); - createToReceiveRecord.add(receiveRecord); - - // 生成库存 - Inventory inventory = iInventoryService.buildInventory(stock.getId(), receivedQty, asn, receiveRecord); - createToInventory.add(inventory); - - //添加库存日志 - InventoryLog inventoryLog = iInventoryLogService.buildInboundInventoryLog(inventory, asnDetail.getToPointId(), receivedQty, asn.getOrderNo(), asnDetail.getId(), asnDetail.getDescription()); - createToInventoryLog.add(inventoryLog); } - - if (CollectionUtils.isNotEmpty(updateToAsnDetail)) { - batchUtil.updateBatchAsnDetail(updateToAsnDetail); - } - if (CollectionUtils.isNotEmpty(createToReceiveRecord)) { - batchUtil.saveBatchReceiveRecord(createToReceiveRecord); - } - if (CollectionUtils.isNotEmpty(createToInventory)) { - batchUtil.saveBatchInventory(createToInventory); - } - if (CollectionUtils.isNotEmpty(createToInventoryLog)) { - batchUtil.saveBatchInventoryLog(createToInventoryLog); - } - - //更新入库单 - refreshAsn(asn, asnDetails); - - //更新容器状态和位置 - iStockService.bindStock(stock, dstPoint); - iPointService.bindPoint(dstPoint); - - //回传 - receiveCallback(asn, stock); } @@ -331,7 +256,7 @@ public class AsnServiceImpl extends ServiceImpl implements IAsnS */ private String receiveCallbackJson(Asn asn, Stock stock, String ticket) { - SaiWmsRequest.Task task = new SaiWmsRequest.Task(); + SMOMRequest.Task task = new SMOMRequest.Task(); task.setNo(asn.getNo()); task.setOrderNo(asn.getThirdOrderNo()); task.setState(5); @@ -343,17 +268,17 @@ public class AsnServiceImpl extends ServiceImpl implements IAsnS task.setIsDelete(false); task.setLastUpdateDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); - SaiWmsRequest.ParameterValue1 parameterValue1 = new SaiWmsRequest.ParameterValue1(); + SMOMRequest.ParameterValue1 parameterValue1 = new SMOMRequest.ParameterValue1(); parameterValue1.setValue(List.of(task)); - SaiWmsRequest.ParameterValue2 parameterValue2 = new SaiWmsRequest.ParameterValue2(); + SMOMRequest.ParameterValue2 parameterValue2 = new SMOMRequest.ParameterValue2(); parameterValue2.setValue(1); - SaiWmsRequest.Context context = new SaiWmsRequest.Context(); + SMOMRequest.Context context = new SMOMRequest.Context(); context.setInvOrgId(1); context.setTicket(ticket); - SaiWmsRequest saiWmsRequest = new SaiWmsRequest(); + SMOMRequest saiWmsRequest = new SMOMRequest(); saiWmsRequest.setApiType("SmomWebApiController"); saiWmsRequest.setParameters(List.of(parameterValue1, parameterValue2)); saiWmsRequest.setMethod("AutomatedWarehouseTasks"); @@ -434,26 +359,4 @@ public class AsnServiceImpl extends ServiceImpl implements IAsnS asnDetailMapper.updateById(updateToAsnDetail); } } - - /** - * 创建入库记录 - */ - public ReceiveRecord buildReceiveRecord(AsnDetail asnDetail, BigDecimal receivedQty, ItemKey itemKey, Long dstPointId) { - return ReceiveRecord.builder() - .id(IdWorker.getId()) - .asnDetailId(asnDetail.getId()) - .stockId(asnDetail.getStockId()) - .fromPointId(asnDetail.getToPointId()) - .toPointId(dstPointId) - .itemId(asnDetail.getItemId()) - .itemKeyId(itemKey.getId()) - .receivedQty(receivedQty) - .description(asnDetail.getDescription()) - .tenantId(asnDetail.getTenantId()) - .sysOrgCode(asnDetail.getSysOrgCode()) - .createBy(asnDetail.getCreateBy()) - .createTime(new Date()) - .build(); - } - } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/vo/ReceiveData.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/vo/ReceiveData.java new file mode 100644 index 0000000..738af81 --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/receive/vo/ReceiveData.java @@ -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 asnDetails; + private Stock stock; + private Point dstPoint; +} diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/controller/SaiWmsController.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/controller/SaiWmsController.java index a3edbd3..ed47804 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/controller/SaiWmsController.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/controller/SaiWmsController.java @@ -7,10 +7,9 @@ import lombok.extern.slf4j.Slf4j; import org.cpte.modules.saiWms.request.InboundRequest; import org.cpte.modules.saiWms.request.OutboundRequest; import org.cpte.modules.saiWms.request.SyncStockRequest; -import org.cpte.modules.saiWms.service.ISaiWmsService; +import org.cpte.modules.saiWms.service.ISMOMService; import org.jeecg.common.api.vo.Result; import org.jeecg.common.aspect.annotation.AutoLog; -import org.jeecg.config.shiro.IgnoreAuth; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -24,7 +23,7 @@ import org.springframework.web.bind.annotation.RestController; public class SaiWmsController { @Autowired - private ISaiWmsService iSaiWmsService; + private ISMOMService iSaiWmsService; /** * 容器同步 diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/request/OutboundRequest.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/request/OutboundRequest.java index 6c1acf1..1860733 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/request/OutboundRequest.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/request/OutboundRequest.java @@ -68,7 +68,7 @@ public class OutboundRequest { // 数量 @NotNull(message = "数量不能为空") @JsonProperty("Qty") - private BigDecimal qty; + private Double qty; // 项目号 @JsonProperty("Project") diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/request/SaiWmsRequest.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/request/SMOMRequest.java similarity index 99% rename from cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/request/SaiWmsRequest.java rename to cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/request/SMOMRequest.java index 6b81f69..a366f00 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/request/SaiWmsRequest.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/request/SMOMRequest.java @@ -6,7 +6,7 @@ import lombok.Data; import java.util.List; @Data -public class SaiWmsRequest { +public class SMOMRequest { @JsonProperty("ApiType") private String apiType; diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/ISaiWmsService.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/ISMOMService.java similarity index 94% rename from cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/ISaiWmsService.java rename to cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/ISMOMService.java index f148f80..d0523cd 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/ISaiWmsService.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/ISMOMService.java @@ -4,7 +4,7 @@ import org.cpte.modules.saiWms.request.InboundRequest; import org.cpte.modules.saiWms.request.OutboundRequest; import org.cpte.modules.saiWms.request.SyncStockRequest; -public interface ISaiWmsService { +public interface ISMOMService { /** * 入库任务下发 diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/InBoundTaskProcessor.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/InBoundTaskProcessor.java new file mode 100644 index 0000000..8117ae3 --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/InBoundTaskProcessor.java @@ -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 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 itemMap, Point srcPoint, Stock stock) { + //1.获取终点 + Point dstPoint = getDstPoint(inboundRequest.getType()); + + //2.构建入库单和入库明细 + Asn createAsn = asnService.buildAsn(inboundRequest); + List 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 lpns = inboundRequest.getDetails().stream().map(InboundRequest.InboundDetail::getLpn).collect(Collectors.toSet()); + if (lpns.size() > 1) { + throw new RuntimeException("明细中托盘只能是同一个"); + } + + //验证明细中物料只能是同一种,如果明细中物料有不一样的则提示 + Set 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 + */ + private Map validateItem(List detail) { + //获取明细中所有的物料 + List itemCodes = detail.stream().map(InboundRequest.InboundDetail::getItem).toList(); + + //获取存在的物料 + Map exitItemMap = itemService.queryByItemCodesToMap(itemCodes); + + //获取数据库不存在的物料集合且去重 + List 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; + } + +} diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/OutBoundTaskProcessor.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/OutBoundTaskProcessor.java new file mode 100644 index 0000000..ae57cfd --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/OutBoundTaskProcessor.java @@ -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 itemMap = validateItem(outboundRequest.getDetails()); + + //5.出库处理 + processOutBoundTask(outboundRequest, itemMap); + } + + /** + * 出库处理 + * + * @param outboundRequest 出库参数 + * @param itemMap 物料 + */ + @Transactional(rollbackFor = Exception.class) + private void processOutBoundTask(OutboundRequest outboundRequest, Map itemMap) { + // 创建出库单和明细 + Pick createPick = pickService.buildPick(outboundRequest); + List 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 + */ + private Map validateItem(List detail) { + //获取明细中所有的物料 + List itemCodes = detail.stream().map(OutboundRequest.OutboundDetail::getItem).toList(); + + //获取数据库已存在物料 + Map exitItemMap = itemService.queryByItemCodesToMap(itemCodes); + + //获取不存在的物料 + List 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; + } + +} diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/SyncStockProcessor.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/SyncStockProcessor.java new file mode 100644 index 0000000..ca0307e --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/SyncStockProcessor.java @@ -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 stockMap = getStocks(syncStockRequest.getStocks()); + + //3.创建数据结构 + List insertToStock = new ArrayList<>(); + List 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 + */ + private Map getStocks(List stocks) { + List 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 stocks, Map stockMap, List insertToStock, List updateToStock) { + Set 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 insertToStock, List updateToStock) { + if (CollectionUtils.isNotEmpty(insertToStock)) { + batchUtil.saveBatchStock(insertToStock); + } + if (CollectionUtils.isNotEmpty(updateToStock)) { + batchUtil.batchUpdateStocks(updateToStock); + } + } + +} diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/impl/ISMOMServiceImpl.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/impl/ISMOMServiceImpl.java new file mode 100644 index 0000000..4af6962 --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/impl/ISMOMServiceImpl.java @@ -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); + } +} + + diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/impl/ISaiWmsServiceImpl.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/impl/ISaiWmsServiceImpl.java deleted file mode 100644 index c7bc665..0000000 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/saiWms/service/impl/ISaiWmsServiceImpl.java +++ /dev/null @@ -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 lpns = inboundRequest.getDetails().stream().map(InboundRequest.InboundDetail::getLpn).collect(Collectors.toSet()); - if (lpns.size() > 1) { - throw new RuntimeException("明细中托盘只能是同一个"); - } - - //验证明细中物料只能是同一种,如果明细中物料有不一样的则提示 - Set 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 detail = inboundRequest.getDetails(); - - //获取明细中所有的物料 - List itemCodes = detail.stream().map(InboundRequest.InboundDetail::getItem).toList(); - - //获取存在的物料 - Map exitItemMap = itemService.queryByItemCodesToMap(itemCodes); - - //获取数据库不存在的物料集合且去重 - List 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 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 itemCodes = outboundRequest.getDetails().stream().map(OutboundRequest.OutboundDetail::getItem).distinct().toList(); - - //获取数据库已存在物料 - Map exitItemMap = itemService.queryByItemCodesToMap(itemCodes); - - //获取不存在的物料 - List notExitItemCodes = itemCodes.stream().filter(itemCode -> !exitItemMap.containsKey(itemCode)).toList(); - if (CollectionUtils.isNotEmpty(notExitItemCodes)) { - throw new RuntimeException("系统无" + notExitItemCodes + "物料,请维护"); - } - - // 创建出库单和明细 - Pick createPick = pickService.buildPick(outboundRequest); - List 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 stocks = syncStockRequest.getStocks().stream().map(SyncStockRequest.StockDTO::getCode).distinct().toList(); - - Map stocksList = iStockService.queryByStockCodesToMap(stocks); - - List insertToStock = new ArrayList<>(); - List updateToStock = new ArrayList<>(); - Set 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); - } - - } -} - - diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/controller/PickController.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/controller/PickController.java index a630944..d3e6191 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/controller/PickController.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/controller/PickController.java @@ -10,6 +10,8 @@ import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.cpte.modules.constant.GeneralConstant; import org.cpte.modules.serialNumber.PickSerialNumberRule; +import org.cpte.modules.shipping.entity.Task; +import org.cpte.modules.shipping.service.ITaskService; import org.jeecgframework.poi.excel.ExcelImportUtil; import org.jeecgframework.poi.excel.def.NormalExcelConstants; import org.jeecgframework.poi.excel.entity.ExportParams; @@ -57,6 +59,8 @@ public class PickController { @Autowired private IPickDetailService pickDetailService; @Autowired + private ITaskService taskService; + @Autowired private PickSerialNumberRule pickSerialNumberRule; /** @@ -183,6 +187,13 @@ public class PickController { return Result.OK(pickDetailList); } + @Operation(summary = "Task主表ID查询") + @GetMapping(value = "/queryTaskByMainId") + public Result> queryTaskByMainId(@RequestParam(name = "id", required = true) Long id) { + List pickDetailList = taskService.queryTaskByMainId(id); + return Result.OK(pickDetailList); + } + /** * 导出excel * diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/entity/Task.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/entity/Task.java index 819a1d2..dc85fbb 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/entity/Task.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/entity/Task.java @@ -97,6 +97,11 @@ public class Task implements Serializable { @Schema(description = "出库明细ID") @JsonSerialize(using = ToStringSerializer.class) private java.lang.Long pickDetailId; + + @Schema(description = "商品属性ID") + @JsonSerialize(using = ToStringSerializer.class) + private java.lang.Long itemKeyId; + /** * 库存ID */ diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/mapper/PickMapper.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/mapper/PickMapper.java index 3e524ab..f2adad8 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/mapper/PickMapper.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/mapper/PickMapper.java @@ -23,7 +23,7 @@ public interface PickMapper extends BaseMapper { * @param no 任务号 * @return Pick */ - @Select("select * from data_pick where no = #{no} for update ") + @Select("select * from data_pick where no = #{no} ") Pick queryByNo(@Param("no") String no); /** diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/mapper/TaskMapper.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/mapper/TaskMapper.java index 3750425..f35ef33 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/mapper/TaskMapper.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/mapper/TaskMapper.java @@ -21,11 +21,14 @@ public interface TaskMapper extends BaseMapper { * * @return List */ - @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 queryUnallocatedTask(); @Select("SELECT * FROM data_task WHERE agv_task_id = #{agvTaskId} ") List queryByAgvTask(@Param("agvTaskId") Long agvTaskId); List queryByPickIds(@Param("pickIds") List pickIds); + + @Select("SELECT * FROM data_task WHERE pick_id = #{pickId} ") + List queryTaskByMainId(@Param("pickId")Long pickId); } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/AllocateProcessor.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/AllocateProcessor.java new file mode 100644 index 0000000..13bfbe6 --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/AllocateProcessor.java @@ -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 allocatePick(List pickIds) { + // 错误信息去重(LinkedHashSet保证顺序和唯一) + Set errorMsgSet = new LinkedHashSet<>(); + + // 1.数据准备 + AllocationData data = prepareAllocationData(pickIds); + + //2.验证库存 + if (!validateInventory(data, errorMsgSet)) { + return new ArrayList<>(errorMsgSet); + } + + //3.创建数据结构 + Map inventoryUpdateMap = new HashMap<>(); + Map pickDetailUpdateMap = new HashMap<>(); + List 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 pickIds) { + AllocationData data = new AllocationData(); + + //查询出库单 + Map pickMap = pickDetailService.queryByPickIdsToMap(pickIds); + data.setPickMap(pickMap); + + //查询出库单明细 + List pickDetails = pickDetailMapper.queryByPickIds(pickIds); + data.setPickDetails(pickDetails); + + //查询物料 + List itemIds = pickDetails.stream().map(PickDetail::getItemId).distinct().toList(); + Map itemMap = itemService.queryByItemIdsToMap(itemIds); + data.setItemMap(itemMap); + + //筛选查询库存的条件(非空去重)外部仓库、项目号、任务号、批次、外部库存状态 + List whCodeList = pickMap.values().stream().map(Pick::getWhCode).filter(StringUtils::isNotBlank).distinct().toList(); + List projectList = pickDetails.stream().map(PickDetail::getProject).filter(StringUtils::isNotBlank).distinct().toList(); + List taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).filter(StringUtils::isNotBlank).distinct().toList(); + List propC1List = pickDetails.stream().map(PickDetail::getPropC1).filter(StringUtils::isNotBlank).distinct().toList(); + List propC3List = pickDetails.stream().map(PickDetail::getPropC3).filter(StringUtils::isNotBlank).distinct().toList(); + List itemKeys = itemKeyMapper.queryItemKeys(itemIds, whCodeList, projectList, taskNoList, propC1List, propC3List); + + //根据物料属性分组 + Map 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 inventories = null; + List 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> inventoryMap = inventories.stream().collect(Collectors.groupingBy(Inventory::getItemKeyId)); + data.setInventoryMap(inventoryMap); + + //获取容器 + List stockIds = inventories.stream().map(Inventory::getStockId).distinct().toList(); + Map stockMap = stockService.queryByStockIdsToMap(stockIds); + data.setStockMap(stockMap); + + //获取目标库位 + List pointIds = inventories.stream().map(Inventory::getPointId).distinct().toList(); + Map pointMap = pointService.queryByPointIdsToMap(pointIds); + data.setPointMap(pointMap); + + } + return data; + } + + /** + * 验证库存 + * + * @param data AllocationData + * @param errorMsgSet 错误信息 + */ + private boolean validateInventory(AllocationData data, Set errorMsgSet) { + List 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 inventoryUpdateMap, Map pickDetailUpdateMap, List createToTask, Set 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 errorMsgSet, Map inventoryUpdateMap, + Map pickDetailUpdateMap, List 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 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 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 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 matchedInventories, Map inventoryUpdateMap, + Map pickDetailUpdateMap, List createToTask, + AllocationData data, BigDecimal totalUnAllocatedQty, Set errorMsgSet) { + // 智能排序库存 + List 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 + */ + private List scoreInventories(List matchedInventories) { + // 批量查询库位 + List pointIds = matchedInventories.stream() + .map(Inventory::getPointId) + .toList(); + Map pointMap = pointService.queryByPointIdsToMap(pointIds); + + // 按巷道和层分组 + Map> colLayerPointsMap = new HashMap<>(); + + for (Point point : pointMap.values()) { + String key = point.getColNum() + "-" + point.getLayerNum(); + if (colLayerPointsMap.containsKey(key)) { + continue; + } + List points = pointService.findByColAndLayer(point.getColNum(), point.getLayerNum()); + colLayerPointsMap.put(key, points); + } + + //获取出库口的库位 + List 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 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 points, List outPoints) { + // 位移分数 + double moveScore; + //移位库位 + List 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 leftPoints = calculateUsedPoints(points, 0, targetIndex); + + // 计算右侧占用数 + List 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 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 + */ + private List calculateUsedPoints(List points, int start, int end) { + List 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 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 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 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 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 inventoryUpdateMap, Map pickDetailUpdateMap, List createToTask) { + + List updateToInventory = new ArrayList<>(inventoryUpdateMap.values()); + List 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 entry : data.getPickMap().entrySet()) { + pickDetailService.refreshPick(entry.getValue(), pickDetailMapper.selectByMainId(entry.getKey())); + } + } +} diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/CancelAllocateProcessor.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/CancelAllocateProcessor.java new file mode 100644 index 0000000..baeb29f --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/CancelAllocateProcessor.java @@ -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 cancelAllocatePick(List pickIds) { + // 错误信息去重(LinkedHashSet保证顺序和唯一) + Set errorMsgSet = new LinkedHashSet<>(); + + // 1.数据准备 + CancelAllocateData data = prepareCancelAllocateData(pickIds); + + //2.创建数据结构 + Map inventoryUpdateMap = new HashMap<>(); + Map pickDetailUpdateMap = new HashMap<>(); + List 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 pickIds) { + CancelAllocateData data = new CancelAllocateData(); + + //查询出库单 + Map pickMap = pickDetailService.queryByPickIdsToMap(pickIds); + data.setPickMap(pickMap); + + //查询出库单明细 + List pickDetails = pickDetailMapper.queryByPickIds(pickIds); + data.setPickDetails(pickDetails); + + //pickDetails 转成 pickDetailId -> pickDetail + Map pickDetailIdMap = pickDetails.stream().collect(Collectors.toMap(PickDetail::getId, PickDetail -> PickDetail)); + data.setPickDetailIdMap(pickDetailIdMap); + + List tasks = taskMapper.queryByPickIds(pickIds); + data.setTasks(tasks); + + List inventoryIds = tasks.stream().map(Task::getInventoryId).distinct().toList(); + Map 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 inventoryUpdateMap, Map pickDetailUpdateMap, List deleteToTask, Set 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 inventoryUpdateMap, + Map pickDetailUpdateMap, List deleteToTask, Set 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 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 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 inventoryUpdateMap, Map pickDetailUpdateMap, List deleteToTask) { + List updateToInventory = new ArrayList<>(inventoryUpdateMap.values()); + List 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 entry : data.getPickMap().entrySet()) { + pickDetailService.refreshPick(entry.getValue(), pickDetailMapper.selectByMainId(entry.getKey())); + } + } + + +} diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/IPickDetailService.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/IPickDetailService.java index 7ade81a..e78fbc8 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/IPickDetailService.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/IPickDetailService.java @@ -1,8 +1,10 @@ package org.cpte.modules.shipping.service; +import org.cpte.modules.shipping.entity.Pick; import org.cpte.modules.shipping.entity.PickDetail; import com.baomidou.mybatisplus.extension.service.IService; import java.util.List; +import java.util.Map; /** * @Description: 出库明细 @@ -19,4 +21,22 @@ public interface IPickDetailService extends IService { * @return List */ public List selectByMainId(Long mainId); + + /** + * 根据出库单ID查询出库单Map + * + * @param pickIds 出库单ID集合 + * @return Map + */ + Map queryByPickIdsToMap(List pickIds); + + + /** + * 刷新出库单 + * + * @param pick 出库单 + * @param pickDetails 出库单明细 + */ + void refreshPick(Pick pick, List pickDetails); + } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/IPickService.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/IPickService.java index bb01cb9..a439d65 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/IPickService.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/IPickService.java @@ -62,12 +62,13 @@ public interface IPickService extends IService { /** * 构建出库单明细 * - * @param details 出库单明细 + * @param details 出库单明细 * @param exitItemMap 物料 * @return 出库单明细 */ List buildPickDetail(List details, Map exitItemMap); + /** * 分配出库单 * @@ -75,7 +76,6 @@ public interface IPickService extends IService { */ List allocatePick(List pickIds); - /** * 取消分配 * diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/ITaskService.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/ITaskService.java index e77c4f6..d02ea04 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/ITaskService.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/ITaskService.java @@ -50,7 +50,7 @@ public interface ITaskService extends IService { * @param izAll 是否整托 * @return Task */ - Task bulidTask(String taskNo, Integer taskType, Item item, Point fromPoint, Point toPoint, Stock stock, Long pickId, Long pickDetailId, Long inventoryId, BigDecimal planQty, Integer izAll); + Task bulidTask(String taskNo, Integer taskType, Item item, Point fromPoint, Point toPoint, Stock stock, Long pickId, Long pickDetailId,Long itemKeyId, Long inventoryId, BigDecimal planQty, Integer izAll); /** * 根据Task集合生成AGV出库任务 @@ -65,4 +65,5 @@ public interface ITaskService extends IService { */ List bulidMoveTask(List movePointIds); + List queryTaskByMainId(Long id); } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/impl/PickDetailServiceImpl.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/impl/PickDetailServiceImpl.java index ff2b113..ae12350 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/impl/PickDetailServiceImpl.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/impl/PickDetailServiceImpl.java @@ -1,24 +1,109 @@ package org.cpte.modules.shipping.service.impl; +import org.apache.commons.collections4.CollectionUtils; +import org.cpte.modules.constant.enums.PickStatusEnum; +import org.cpte.modules.shipping.entity.Pick; import org.cpte.modules.shipping.entity.PickDetail; import org.cpte.modules.shipping.mapper.PickDetailMapper; +import org.cpte.modules.shipping.mapper.PickMapper; import org.cpte.modules.shipping.service.IPickDetailService; import org.springframework.stereotype.Service; -import java.util.List; + +import java.math.BigDecimal; +import java.util.*; + import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired; /** * @Description: 出库明细 * @author: cpte - * @Date: 2025-11-14 + * @Date: 2025-11-14 * @Version: V1.0 */ @Service public class PickDetailServiceImpl extends ServiceImpl implements IPickDetailService { - - @Override - public List selectByMainId(Long mainId) { - return this.baseMapper.selectByMainId(mainId); + @Autowired + private PickMapper pickMapper; + + @Override + public List selectByMainId(Long mainId) { + return this.baseMapper.selectByMainId(mainId); + } + + /** + * 获取出库单Map + * + * @param pickIds 出库单id + * @return 出库单Map + */ + public Map queryByPickIdsToMap(List pickIds) { + if (CollectionUtils.isEmpty(pickIds)) { + return Collections.emptyMap(); + } + Map pickMap = new HashMap<>(); + List 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 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); + } } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/impl/PickServiceImpl.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/impl/PickServiceImpl.java index b440a03..06c868d 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/impl/PickServiceImpl.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/impl/PickServiceImpl.java @@ -7,22 +7,17 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.SecurityUtils; -import org.cpte.modules.base.entity.ItemKey; -import org.cpte.modules.base.mapper.ItemKeyMapper; import org.cpte.modules.constant.GeneralConstant; import org.cpte.modules.base.entity.Item; import org.cpte.modules.base.entity.Point; -import org.cpte.modules.base.entity.Stock; -import org.cpte.modules.base.service.IItemService; -import org.cpte.modules.base.service.IPointService; -import org.cpte.modules.base.service.IStockService; import org.cpte.modules.constant.enums.*; import org.cpte.modules.inventory.entity.Inventory; -import org.cpte.modules.inventory.mapper.InventoryMapper; import org.cpte.modules.inventory.service.IInventoryService; import org.cpte.modules.inventoryLog.service.IInventoryLogService; +import org.cpte.modules.receive.entity.Asn; +import org.cpte.modules.receive.entity.AsnDetail; import org.cpte.modules.saiWms.request.OutboundRequest; -import org.cpte.modules.saiWms.request.SaiWmsRequest; +import org.cpte.modules.saiWms.request.SMOMRequest; import org.cpte.modules.serialNumber.PickSerialNumberRule; import org.cpte.modules.shipping.entity.Pick; import org.cpte.modules.shipping.entity.PickDetail; @@ -30,13 +25,13 @@ import org.cpte.modules.shipping.entity.Task; import org.cpte.modules.shipping.mapper.PickDetailMapper; import org.cpte.modules.shipping.mapper.PickMapper; import org.cpte.modules.shipping.mapper.TaskMapper; +import org.cpte.modules.shipping.service.AllocateProcessor; +import org.cpte.modules.shipping.service.CancelAllocateProcessor; +import org.cpte.modules.shipping.service.IPickDetailService; import org.cpte.modules.shipping.service.IPickService; -import org.cpte.modules.shipping.service.ITaskService; -import org.cpte.modules.shipping.vo.InventoryGroupKey; -import org.cpte.modules.shipping.vo.InventoryScore; import org.cpte.modules.utils.BatchUtil; import org.cpte.modules.utils.BigDecimalUtil; -import org.cpte.modules.utils.HttpPostUtil; +import org.cpte.modules.utils.RedisDistributedLockUtil; import org.cpte.modules.utils.SwmsLoginUtil; import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.system.vo.LoginUser; @@ -53,7 +48,6 @@ import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; /** * @Description: 出库单 @@ -70,51 +64,29 @@ public class PickServiceImpl extends ServiceImpl implements IP @Autowired private TaskMapper taskMapper; @Autowired - private ItemKeyMapper itemKeyMapper; - @Autowired - private InventoryMapper inventoryMapper; - @Autowired private SysDictMapper sysDictMapper; @Autowired private OpenApiMapper openApiMapper; @Autowired - private IItemService iItemService; - @Autowired - private IStockService iStockService; - @Autowired - private IPointService iPointService; - @Autowired - private ITaskService iTaskService; + private IPickDetailService pickDetailService; @Autowired private IInventoryService inventoryService; @Autowired - private IInventoryLogService iInventoryLogService; - @Autowired - private SwmsLoginUtil swmsLoginUtil; + private IInventoryLogService inventoryLogService; @Autowired private BaseCommonService baseCommonService; @Autowired + private SwmsLoginUtil swmsLoginUtil; + @Autowired private BatchUtil batchUtils; @Autowired private PickSerialNumberRule pickSerialNumberRule; - - /** - * 获取出库单Map - * - * @param pickIds 出库单id - * @return 出库单Map - */ - private Map queryByPickIdsToMap(List pickIds) { - if (CollectionUtils.isEmpty(pickIds)) { - return Collections.emptyMap(); - } - Map pickMap = new HashMap<>(); - List pickList = this.baseMapper.selectByIds(pickIds); - for (Pick pick : pickList) { - pickMap.put(pick.getId(), pick); - } - return pickMap; - } + @Autowired + private AllocateProcessor allocateProcessor; + @Autowired + private CancelAllocateProcessor cancelAllocateProcessor; + @Autowired + private RedisDistributedLockUtil redissonLock; /** * 获取出库单明细Map @@ -134,25 +106,37 @@ public class PickServiceImpl extends ServiceImpl implements IP return pickDetailMap; } - private Map queryPickDetailByPickIds(List pickIds) { - if (CollectionUtils.isEmpty(pickIds)) { - return Collections.emptyMap(); - } - List pickDetails = pickDetailMapper.queryByPickIds(pickIds); - Map pickDetailMap = new HashMap<>(); - for (PickDetail pickDetail : pickDetails) { - pickDetailMap.put(pickDetail.getId(), pickDetail); - } - return pickDetailMap; - } - @Override - @Transactional(rollbackFor = Exception.class) public void saveMain(Pick pick, List 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 pickDetailList) { LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal(); pick.setTenantId(Long.parseLong(sysUser.getRelTenantIds())); pick.setSysOrgCode(sysUser.getOrgCode()); + String orderNo = pickSerialNumberRule.generateSerialNumber(GeneralConstant.PICK_ORDER_NO); + pick.setOrderNo(orderNo); this.baseMapper.insert(pick); if (pickDetailList == null || pickDetailList.isEmpty()) { @@ -173,67 +157,9 @@ public class PickServiceImpl extends ServiceImpl implements IP pickDetailMapper.insert(entity); } //刷新出库单 - refreshPick(pick, pickDetailList); + pickDetailService.refreshPick(pick, pickDetailList); } - /** - * 刷新出库单状态 - * - * @param pick 出库单 - * @param pickDetails 出库明细 - */ - public synchronized void refreshPick(Pick pick, List pickDetails) { - if (pickDetails == null) { - pickDetails = new ArrayList<>(); - } - - // 计算各种数量 - BigDecimal orderQty = pickDetails.stream().map(PickDetail::getOrderQty).reduce(BigDecimal.ZERO, BigDecimal::add); - BigDecimal allocatedQty = pickDetails.stream().map(PickDetail::getAllocatedQty).reduce(BigDecimal.ZERO, BigDecimal::add); - BigDecimal pickedQty = pickDetails.stream().map(PickDetail::getPickedQty).reduce(BigDecimal.ZERO, BigDecimal::add); - - // 当前状态 - Integer status = pick.getStatus(); - - // 如果没有任何需求,则为创建状态 - if (orderQty.compareTo(BigDecimal.ZERO) <= 0) { - status = PickStatusEnum.CREATED.getValue(); - } - // 如果分配数量为0,但有需求数量,则为创建状态 - else if (allocatedQty.compareTo(BigDecimal.ZERO) == 0) { - status = PickStatusEnum.CREATED.getValue(); - } - // 如果已全部分配但未开始拣货 - else if (allocatedQty.compareTo(orderQty) >= 0 && pickedQty.compareTo(BigDecimal.ZERO) == 0) { - status = PickStatusEnum.ASSIGNED.getValue(); - } - // 如果部分分配且未开始拣货 - else if (allocatedQty.compareTo(orderQty) < 0 && pickedQty.compareTo(BigDecimal.ZERO) == 0) { - status = PickStatusEnum.PARTIAL.getValue(); - } - // 如果开始拣货但未完成 - else if (pickedQty.compareTo(BigDecimal.ZERO) > 0 && pickedQty.compareTo(orderQty) < 0) { - status = PickStatusEnum.PICKING.getValue(); - } - // 如果已完成拣货 - else if (pickedQty.compareTo(orderQty) >= 0 && orderQty.compareTo(BigDecimal.ZERO) > 0) { - status = PickStatusEnum.PICKED.getValue(); - } - - //明细的状态都是已关闭则出库单状态为已关闭 - boolean isClosed = pickDetails.stream().allMatch(detail -> PickStatusEnum.CLOSED.getValue().equals(detail.getStatus())); - log.info("出库单明细是否全部关闭:{}", isClosed); - if (isClosed) { - status = PickStatusEnum.CLOSED.getValue(); - } - - // 更新实体属性 - pick.setOrderQty(orderQty); - pick.setAllocatedQty(allocatedQty); - pick.setPickedQty(pickedQty); - pick.setStatus(status); - this.baseMapper.updateById(pick); - } @Override @Transactional(rollbackFor = Exception.class) @@ -277,7 +203,7 @@ public class PickServiceImpl extends ServiceImpl implements IP } // 刷新出库单状态 - refreshPick(pick, pickDetailList); + pickDetailService.refreshPick(pick, pickDetailList); } @Override @@ -311,9 +237,7 @@ public class PickServiceImpl extends ServiceImpl implements IP @Override public Pick buildPick(OutboundRequest outboundRequest) { - String orderNo = pickSerialNumberRule.generateSerialNumber(GeneralConstant.PICK_ORDER_NO); return Pick.builder() - .orderNo(orderNo) .thirdOrderNo(outboundRequest.getOrderNo()) .no(outboundRequest.getNo()) .whCode(outboundRequest.getWhCode()) @@ -332,7 +256,7 @@ public class PickServiceImpl extends ServiceImpl implements IP .lineNo(Integer.parseInt(detail.getLineNo())) .itemId(exitItemMap.get(detail.getItem()).getId()) .unit(detail.getUnit()) - .orderQty(detail.getQty()) + .orderQty(BigDecimal.valueOf(detail.getQty())) .allocatedQty(BigDecimal.ZERO) .pickedQty(BigDecimal.ZERO) .status(PickStatusEnum.CREATED.getValue()) @@ -347,400 +271,18 @@ public class PickServiceImpl extends ServiceImpl implements IP } @Override - @Transactional(rollbackFor = Exception.class) public List allocatePick(List pickIds) { - // 错误信息去重(LinkedHashSet保证顺序和唯一) - Set errorMsgSet = new LinkedHashSet<>(); - - // -------------------------- 1. 查询关联数据(批量查询,减少DB交互)-------------------------- - //查询出库单 - Map pickMap = queryByPickIdsToMap(pickIds); - - //查询出库单明细 - List pickDetails = pickDetailMapper.queryByPickIds(pickIds); - - //获取物料 - List itemIds = pickDetails.stream().map(PickDetail::getItemId).distinct().toList(); - Map itemMap = iItemService.queryByItemIdsToMap(itemIds); - - //筛选查询库存的条件(非空去重)外部仓库、项目号、任务号、批次、外部库存状态 - List whCodeList = pickMap.values().stream().map(Pick::getWhCode).filter(StringUtils::isNotBlank).distinct().toList(); - List projectList = pickDetails.stream().map(PickDetail::getProject).filter(StringUtils::isNotBlank).distinct().toList(); - List taskNoList = pickDetails.stream().map(PickDetail::getTaskNo).filter(StringUtils::isNotBlank).distinct().toList(); - List propC1List = pickDetails.stream().map(PickDetail::getPropC1).filter(StringUtils::isNotBlank).distinct().toList(); - List propC3List = pickDetails.stream().map(PickDetail::getPropC3).filter(StringUtils::isNotBlank).distinct().toList(); - List itemKeys = itemKeyMapper.queryItemKeys(itemIds, whCodeList, projectList, taskNoList, propC1List, propC3List); - List itemKeyIds = itemKeys.stream().map(ItemKey::getId).distinct().toList(); - - //查询库存 - List 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> inventoryMap = inventories.stream().collect(Collectors.groupingBy(Inventory::getItemKeyId)); - - //获取容器 - List stockIds = inventories.stream().map(Inventory::getStockId).distinct().toList(); - Map stockMap = iStockService.queryByStockIdsToMap(stockIds); - - //获取目标库位 - List pointIds = inventories.stream().map(Inventory::getPointId).distinct().toList(); - Map pointMap = iPointService.queryByPointIdsToMap(pointIds); - - - // -------------------------- 2. 库存分组排序(优化匹配效率)-------------------------- - // 按「itemKeyId分组 - Map 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 updateToInventory = new ArrayList<>(); - List updateToPickDetail = new ArrayList<>(); - List 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 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 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 movePointIds = inventoryScore.getMovePoints().stream().map(Point::getId).toList(); - createToTask.addAll(iTaskService.bulidMoveTask(movePointIds)); - } else { - log.info("无移位任务"); - } - - //分配库存日志 - iInventoryLogService.addAllocInventoryLog(inventory, toPoint.getId(), allocateQty, pick.getOrderNo(), pickDetail.getId(), pickDetail.getDescription()); - //更新未分配数量 - unAllocatedQty = BigDecimalUtil.subtract(unAllocatedQty, allocateQty, 0); - } - //分配后仍有缺货,记录错误 - if (unAllocatedQty.compareTo(BigDecimal.ZERO) > 0) { - String failInfo = String.format( - "物料【%s】库存不足(任务号:%s,行号:%d,需求:%s,已分配:%s,缺货:%s)", - item.getItemCode(), - pick.getNo(), - pickDetail.getLineNo(), - pickDetail.getOrderQty(), - BigDecimalUtil.subtract(pickDetail.getOrderQty(), unAllocatedQty, 0), - unAllocatedQty - ); - errorMsgSet.add(failInfo); - } - } - - // -------------------------- 5. 批量更新(减少DB交互)-------------------------- - if (CollectionUtils.isNotEmpty(updateToInventory)) { - batchUtils.updateBatchInventory(updateToInventory); - } - if (CollectionUtils.isNotEmpty(updateToPickDetail)) { - batchUtils.updateBatchPickDetail(updateToPickDetail); - } - if (CollectionUtils.isNotEmpty(createToTask)) { - batchUtils.saveBatchTask(createToTask); - } - - // -------------------------- 6. 刷新出库单状态 -------------------------- - for (Pick pick : pickMap.values()) { - refreshPick(pick, pickDetailMapper.selectByMainId(pick.getId())); - } - return new ArrayList<>(errorMsgSet); + return allocateProcessor.allocatePick(pickIds); } - /** - * 计算库存得分 - * - * @param matchedInventories 库存集合 - * @return List - */ - private List scoreInventories(List matchedInventories) { - // 批量查询库位 - List pointIds = matchedInventories.stream() - .map(Inventory::getPointId) - .toList(); - Map pointMap = iPointService.queryByPointIdsToMap(pointIds); - - // 按巷道和层分组 - Map> colLayerPointsMap = new HashMap<>(); - - for (Point point : pointMap.values()) { - String key = point.getColNum() + "-" + point.getLayerNum(); - if (colLayerPointsMap.containsKey(key)) { - continue; - } - List points = iPointService.findByColAndLayer(point.getColNum(), point.getLayerNum()); - colLayerPointsMap.put(key, points); - } - - //获取出库口的库位 - List 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 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 points, List outPoints) { - // 位移分数 - double moveScore; - //移位库位 - List 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 leftPoints = calculateUsedPoints(points, 0, targetIndex); - - // 计算右侧占用数 - List 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 - */ - private List calculateUsedPoints(List points, int start, int end) { - List 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 outPoints) { - //获取距离最近的出库口 - return outPoints.stream() - .min(Comparator.comparingDouble(point -> - Math.abs(currPoint.getPositionX() - point.getPositionX()) + - Math.abs(currPoint.getPositionY() - point.getPositionY()))) - .orElse(null); - - } - - /** - * 计算距离评分 - * 基于出库口位置和库位坐标计算最短路径距离 - */ - private double calculateClusterDistanceCost(Point point, Point station) { - // 计算曼哈顿距离 - double distance = Math.abs(point.getPositionX() - station.getPositionX()) + Math.abs(point.getPositionY() - station.getPositionY()); - - // 距离越小分数越高 - return Math.max(0, 100 - (distance / 100.0)); - } - - @Override - @Transactional(rollbackFor = Exception.class) public List cancelAllocate(List pickIds) { - Set errorMsgSet = new LinkedHashSet<>(); - // -------------------------- 1. 查询关联数据(批量查询,减少DB交互)-------------------------- - Map pickMap = queryByPickIdsToMap(pickIds); - - Map pickDetailMap = queryPickDetailByPickIds(pickIds); - - List tasks = taskMapper.queryByPickIds(pickIds); - - List inventoryIds = tasks.stream().map(Task::getInventoryId).distinct().toList(); - Map inventoryMap = inventoryService.queryByInventoryIdsToMap(inventoryIds); - - // -------------------------- 2. 创建更新列表--------------------------- - List updateToInventory = new ArrayList<>(); - List updateToPickDetail = new ArrayList<>(); - List deleteToTask = new ArrayList<>(); - - // -------------------------- 3. 循环处理任务--------------------------- - for (Task task : tasks) { - Pick pick = pickMap.get(task.getPickId()); - PickDetail pickDetail = pickDetailMap.get(task.getPickDetailId()); - if (task.getAgvTaskId() != null) { - errorMsgSet.add(String.format("取消失败:【%s】已生成AGV任务", pick.getNo())); - continue; - } - //取消数量 - BigDecimal cancelQty = task.getPlanQty(); - - Inventory inventory = inventoryMap.get(task.getInventoryId()); - inventory.setQueuedQty(BigDecimalUtil.subtract(inventory.getQueuedQty(), cancelQty, 0)); - Integer inv_status = inventory.getQueuedQty().compareTo(BigDecimal.ZERO) > 0 ? InventoryStatusEnum.ALLOCATED.getValue() : InventoryStatusEnum.AVAILABLE.getValue(); - inventory.setStatus(inv_status); - updateToInventory.add(inventory); - - - pickDetail.setAllocatedQty(BigDecimalUtil.subtract(pickDetail.getAllocatedQty(), cancelQty, 0)); - Integer pd_status = pickDetail.getStatus(); - if (pickDetail.getAllocatedQty().compareTo(BigDecimal.ZERO) <= 0) { - pd_status = PickStatusEnum.CREATED.getValue(); - } else if (pickDetail.getAllocatedQty().compareTo(pickDetail.getOrderQty()) < 0) { - pd_status = PickStatusEnum.PARTIAL.getValue(); - } - pickDetail.setStatus(pd_status); - updateToPickDetail.add(pickDetail); - - deleteToTask.add(task); - - iInventoryLogService.addUnAllocInventoryLog(inventory, cancelQty, pick.getOrderNo(), pickDetail.getId(), pickDetail.getDescription()); - - } - - // -------------------------- 4. 批量更新(减少DB交互)-------------------------- - if (CollectionUtils.isNotEmpty(updateToInventory)) { - batchUtils.updateBatchInventory(updateToInventory); - } - if (CollectionUtils.isNotEmpty(updateToPickDetail)) { - batchUtils.updateBatchPickDetail(updateToPickDetail); - } - if (CollectionUtils.isNotEmpty(deleteToTask)) { - taskMapper.deleteByIds(deleteToTask); - } - - // -------------------------- 5. 刷新出库单状态 -------------------------- - for (Pick pick : pickMap.values()) { - refreshPick(pick, pickDetailMapper.selectByMainId(pick.getId())); - } - - return new ArrayList<>(errorMsgSet); - + return cancelAllocateProcessor.cancelAllocatePick(pickIds); } @Override @Transactional(rollbackFor = Exception.class) public void pickTask(List tasks, Point endPoint) { - if (CollectionUtils.isEmpty(tasks)) { - throw new RuntimeException("无拣货任务"); - } // ================= 1. 数据准备 (批量查询) ================= // 1.1 获取出库单 @@ -749,7 +291,7 @@ public class PickServiceImpl extends ServiceImpl implements IP List inventoryIds = tasks.stream().map(Task::getInventoryId).distinct().toList(); // 1.2 构建映射 Map - Map pickMap = queryByPickIdsToMap(pickIds); + Map pickMap = pickDetailService.queryByPickIdsToMap(pickIds); Map pickDetailMap = queryByPickDetailIdsToMap(pickDetailIds); Map inventoryMap = inventoryService.queryByInventoryIdsToMap(inventoryIds); @@ -791,7 +333,7 @@ public class PickServiceImpl extends ServiceImpl implements IP deleteToInventoryIds.add(inventory.getId()); } - iInventoryLogService.addPickInventoryLog(inventory, pickedQty, pick.getOrderNo(), task.getPickDetailId(), null); + inventoryLogService.addPickInventoryLog(inventory, pickedQty, pick.getOrderNo(), task.getPickDetailId(), null); } // -------------------------- 4. 批量操作 -------------------------- @@ -834,7 +376,7 @@ public class PickServiceImpl extends ServiceImpl implements IP // 刷新时重新查询最新的数据 List latestPickDetails = pickDetailMapper.selectByMainId(pick.getId()); pickDetailsCache.put(pickId, latestPickDetails); // 更新缓存 - refreshPick(pick, latestPickDetails); + pickDetailService.refreshPick(pick, latestPickDetails); } } @@ -843,12 +385,12 @@ public class PickServiceImpl extends ServiceImpl implements IP * 出库库任务回传JSON */ private String pickTaskCallbackJson(Pick pick, PickDetail pickDetail, Task task, Integer state, String ticket) { - SaiWmsRequest.Task taskReq = new SaiWmsRequest.Task(); + SMOMRequest.Task taskReq = new SMOMRequest.Task(); taskReq.setNo(pick.getNo()); taskReq.setOrderNo(pick.getThirdOrderNo()); taskReq.setState(state); - SaiWmsRequest.ShipmentFeedbackDetail shipmentFeedbackDetail = new SaiWmsRequest.ShipmentFeedbackDetail(); + SMOMRequest.ShipmentFeedbackDetail shipmentFeedbackDetail = new SMOMRequest.ShipmentFeedbackDetail(); shipmentFeedbackDetail.setLineNo(String.valueOf(pickDetail.getLineNo())); shipmentFeedbackDetail.setLpn(task.getStockCode()); shipmentFeedbackDetail.setQty(task.getPlanQty().intValue()); @@ -869,17 +411,17 @@ public class PickServiceImpl extends ServiceImpl implements IP taskReq.setLastUpdateDate(lastUpdateDate); - SaiWmsRequest.ParameterValue1 parameterValue1 = new SaiWmsRequest.ParameterValue1(); + SMOMRequest.ParameterValue1 parameterValue1 = new SMOMRequest.ParameterValue1(); parameterValue1.setValue(List.of(taskReq)); - SaiWmsRequest.ParameterValue2 parameterValue2 = new SaiWmsRequest.ParameterValue2(); + SMOMRequest.ParameterValue2 parameterValue2 = new SMOMRequest.ParameterValue2(); parameterValue2.setValue(1); - SaiWmsRequest.Context context = new SaiWmsRequest.Context(); + SMOMRequest.Context context = new SMOMRequest.Context(); context.setInvOrgId(1); context.setTicket(ticket); - SaiWmsRequest saiWmsRequest = new SaiWmsRequest(); + SMOMRequest saiWmsRequest = new SMOMRequest(); saiWmsRequest.setApiType("SmomWebApiController"); saiWmsRequest.setParameters(List.of(parameterValue1, parameterValue2)); saiWmsRequest.setMethod("OutboundTaskCallbackInterface"); @@ -892,7 +434,7 @@ public class PickServiceImpl extends ServiceImpl implements IP public void pickTaskCallback(Pick pick, PickDetail pickDetail, Task task, Integer state) { // 检查接口开关, 未开启则返回 if (sysDictMapper.queryByDictCode(GeneralConstant.OPEN_FLAG) == null) { - updatePickDetailResponse(pickDetail,task, GeneralConstant.SMOM_FAIL_CODE, "接口未开启"); + updatePickDetailResponse(pickDetail, task, GeneralConstant.SMOM_FAIL_CODE, "接口未开启"); return; } @@ -912,9 +454,9 @@ public class PickServiceImpl extends ServiceImpl implements IP String url = openApiMapper.getRequestUrl(GeneralConstant.OUTBOUND_CALLBACK).getOriginUrl(); JSONObject jsonObject = swmsLoginUtil.sendSMOMResponse(json, url, authorization); String code = validateResponse(jsonObject); - updatePickDetailResponse(pickDetail,task, code, jsonObject.toJSONString()); + updatePickDetailResponse(pickDetail, task, code, jsonObject.toJSONString()); } catch (Exception e) { - updatePickDetailResponse(pickDetail,task, GeneralConstant.SMOM_FAIL_CODE, e.getMessage()); + updatePickDetailResponse(pickDetail, task, GeneralConstant.SMOM_FAIL_CODE, e.getMessage()); } } @@ -944,9 +486,9 @@ public class PickServiceImpl extends ServiceImpl implements IP * @param task 出库任务 * @param message 信息 */ - private void updatePickDetailResponse(PickDetail pickDetail,Task task, String code, String message) { + private void updatePickDetailResponse(PickDetail pickDetail, Task task, String code, String message) { if (GeneralConstant.SMOM_SUCCESS_CODE.equals(code)) { - if(PickStatusEnum.PICKED.getValue().equals(pickDetail.getStatus())){ + if (PickStatusEnum.PICKED.getValue().equals(pickDetail.getStatus())) { pickDetail.setStatus(PickStatusEnum.CLOSED.getValue()); pickDetailMapper.updateById(pickDetail); } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/impl/TaskServiceImpl.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/impl/TaskServiceImpl.java index 65e3ba0..001cbf5 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/impl/TaskServiceImpl.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/service/impl/TaskServiceImpl.java @@ -103,7 +103,7 @@ public class TaskServiceImpl extends ServiceImpl implements IT } @Override - public Task bulidTask(String taskNo, Integer taskType, Item item, Point fromPoint, Point toPoint, Stock stock, Long pickId, Long pickDetailId, Long inventoryId, BigDecimal planQty, Integer izAll) { + public Task bulidTask(String taskNo, Integer taskType, Item item, Point fromPoint, Point toPoint, Stock stock, Long pickId, Long pickDetailId,Long itemKeyId, Long inventoryId, BigDecimal planQty, Integer izAll) { LoginUser sysUser = null; try { sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal(); @@ -122,6 +122,7 @@ public class TaskServiceImpl extends ServiceImpl implements IT .stockCode(stock.getStockCode()) .pickId(pickId) .pickDetailId(pickDetailId) + .itemKeyId(itemKeyId) .inventoryId(inventoryId) .planQty(planQty) .moveQty(BigDecimal.ZERO) @@ -223,7 +224,7 @@ public class TaskServiceImpl extends ServiceImpl implements IT String taskNo = moveSerialNumberRule.generateSerialNumber(GeneralConstant.MOVE_ORDER_NO); //根据算法找到最优的目标库位 Point toPoint=null; - Task moveTask = this.bulidTask(taskNo, TaskTypeEnum.MOVE.getValue(), moveItem, fromPoint, toPoint, stock, null, null, inv.getId(), inv.getQuantity(), 0); + Task moveTask = this.bulidTask(taskNo, TaskTypeEnum.MOVE.getValue(), moveItem, fromPoint, toPoint, stock, null, null,inv.getItemKeyId(), inv.getId(), inv.getQuantity(), 0); moveList.add(moveTask); log.info("生成移位任务:{}- 容器:{} - 库位:{} - 库存数量:{}", taskNo, stock.getStockCode(), fromPoint.getPointCode(), inv.getQuantity()); inv.setStatus(InventoryStatusEnum.TRANSFER.getValue()); @@ -232,6 +233,11 @@ public class TaskServiceImpl extends ServiceImpl implements IT return moveList; } + @Override + public List queryTaskByMainId(Long id) { + return this.baseMapper.queryTaskByMainId(id); + } + /** * 选择最优移位目标 */ diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/AllocationData.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/AllocationData.java new file mode 100644 index 0000000..164a999 --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/AllocationData.java @@ -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 pickMap; + private List pickDetails; + private Map itemMap; + private List inventories; + //根据itemKeyId分组 + private Map> inventoryMap; + private Map stockMap; + private Map pointMap; + //根据物料属性分组 + private Map itemGroupMap; + +} diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/CancelAllocateData.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/CancelAllocateData.java new file mode 100644 index 0000000..ed9142a --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/CancelAllocateData.java @@ -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 pickMap; + private List pickDetails; + private Map pickDetailIdMap; + private List tasks; + private Map inventoryMap; +} diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/InventoryGroupKey.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/ItemGroupKey.java similarity index 81% rename from cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/InventoryGroupKey.java rename to cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/ItemGroupKey.java index 156af6a..6162023 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/InventoryGroupKey.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/shipping/vo/ItemGroupKey.java @@ -10,7 +10,7 @@ import org.apache.commons.lang3.StringUtils; @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode -public class InventoryGroupKey { +public class ItemGroupKey { private Long itemId;//物料 private String whCode; // 仓库代码 private String project;//项目号 @@ -19,8 +19,8 @@ public class InventoryGroupKey { private String propC3; // 库存状态 - public static InventoryGroupKey of(Long itemId, String whCode, String project, String taskNo, String propC1, String propC3) { - return new InventoryGroupKey( + public static ItemGroupKey of(Long itemId, String whCode, String project, String taskNo, String propC1, String propC3) { + return new ItemGroupKey( itemId, StringUtils.defaultIfBlank(whCode, ""), StringUtils.defaultIfBlank(project, ""), diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/tesAgv/controller/TesAgvController.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/tesAgv/controller/TesAgvController.java index 55c988b..f35bc2b 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/tesAgv/controller/TesAgvController.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/tesAgv/controller/TesAgvController.java @@ -9,9 +9,8 @@ import org.cpte.modules.constant.GeneralConstant; import org.cpte.modules.tesAgv.request.CancelTaskRequest; import org.cpte.modules.tesAgv.request.NewMovePodTaskRequest; import org.cpte.modules.tesAgv.request.TesCallbackRequest; -import org.cpte.modules.tesAgv.response.TesResult; import org.cpte.modules.tesAgv.service.ITesAgvService; -import org.jeecg.common.api.vo.Result; +import org.jeecg.common.api.vo.TesResult; import org.jeecg.common.aspect.annotation.AutoLog; import org.jeecg.config.shiro.IgnoreAuth; import org.springframework.beans.factory.annotation.Autowired; diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/tesAgv/service/impl/ITesAgvServiceImpl.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/tesAgv/service/impl/ITesAgvServiceImpl.java index 718a674..ec946ea 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/tesAgv/service/impl/ITesAgvServiceImpl.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/tesAgv/service/impl/ITesAgvServiceImpl.java @@ -49,19 +49,19 @@ public class ITesAgvServiceImpl implements ITesAgvService { private OpenApiMapper openApiMapper; @Autowired - private IPointService iPointService; + private IPointService pointService; @Autowired - private IStockService iStockService; + private IStockService stockService; @Autowired - private IAsnService iAsnService; + private IAsnService asnService; @Autowired - private IPickService iPickService; + private IPickService pickService; @Autowired - private IAgvTaskService iAgvTaskService; + private IAgvTaskService agvTaskService; @Override public String generateTesAgvTaskJson(AgvTask agvTask) { @@ -229,23 +229,23 @@ public class ITesAgvServiceImpl implements ITesAgvService { private void handleEnd(Long asnId, AgvTask agvTask) { if (BusinessTypeEnum.INBOUND.getValue().equals(agvTask.getType())) { //收货 - iAsnService.receiveGoods(asnId, agvTask.getEndCode()); + asnService.receiveAsn(asnId, agvTask.getEndCode()); } else if (BusinessTypeEnum.OUTBOUND.getValue().equals(agvTask.getType())) { //拣货 - Point endPoint = iPointService.validatePoint(agvTask.getEndCode()); + Point endPoint = pointService.validatePoint(agvTask.getEndCode()); List tasks = taskMapper.queryByAgvTask(agvTask.getId()); - iPickService.pickTask(tasks, endPoint); - Point startPoint = iPointService.validatePoint(agvTask.getStartCode()); + pickService.pickTask(tasks, endPoint); + Point startPoint = pointService.validatePoint(agvTask.getStartCode()); - Stock stock = iStockService.validateStock(agvTask.getCarrierCode()); - iPointService.unbindPoint(startPoint); - iStockService.bindStock(stock, endPoint); + Stock stock = stockService.validateStock(agvTask.getCarrierCode()); + pointService.unbindPoint(startPoint); + stockService.bindStock(stock, endPoint); //判断AGV任务是否整托,整托生成AGV搬运任务 if (agvTask.getIzAll() == 0) { //查询电梯口点位作为目标点位 - String endCode = iPointService.getElevatorPoint(agvTask.getEndCode(), GeneralConstant.CK_ELEVATOR_TASK_INDEX); - iAgvTaskService.createAgvTask(null, agvTask.getCarrierCode(), agvTask.getEndCode(), endCode, null, BusinessTypeEnum.OUTBOUND.getValue(), 0,AgvVendorEnum.HIK.getValue()); + String endCode = pointService.getElevatorPoint(agvTask.getEndCode(), GeneralConstant.CK_ELEVATOR_TASK_INDEX); + agvTaskService.createAgvTask(null, agvTask.getCarrierCode(), agvTask.getEndCode(), endCode, null, BusinessTypeEnum.OUTBOUND.getValue(), 0, AgvVendorEnum.HIK.getValue()); } } @@ -271,11 +271,10 @@ public class ITesAgvServiceImpl implements ITesAgvService { * @param agvTask 任务 */ private void handleResend(AgvTask agvTask) { - Long count = agvTaskMapper.existsByStockCode(agvTask.getCarrierCode(), AgvVendorEnum.TES.getValue()); - if (count > 0) { + if (agvTaskMapper.existsByStockCode(agvTask.getCarrierCode(), AgvVendorEnum.TES.getValue()) != null) { throw new RuntimeException("任务已重新生成,请勿重复操作! "); } - AgvTask newAgvTask = iAgvTaskService.createAgvTask(agvTask.getBusinessDetailId(), agvTask.getCarrierCode(), agvTask.getStartCode(), agvTask.getEndCode(), null, agvTask.getType(),agvTask.getIzAll(), AgvVendorEnum.TES.getValue()); + AgvTask newAgvTask = agvTaskService.createAgvTask(agvTask.getBusinessDetailId(), agvTask.getCarrierCode(), agvTask.getStartCode(), agvTask.getEndCode(), null, agvTask.getType(), agvTask.getIzAll(), AgvVendorEnum.TES.getValue()); switch (agvTask.getType()) { case "INBOUND": case "OUTBOUND": diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/BatchUtil.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/BatchUtil.java index afee71f..dd3c439 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/BatchUtil.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/BatchUtil.java @@ -14,6 +14,7 @@ import org.cpte.modules.shipping.entity.Task; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,7 +25,7 @@ import java.sql.Timestamp; import java.util.List; import java.util.function.BiConsumer; -@Service +@Component public class BatchUtil { @Autowired @@ -286,8 +287,8 @@ public class BatchUtil { */ @Transactional public void saveBatchTask(List tasks) { - String sql = "INSERT INTO data_task (id,task_no,item_id,item_code,from_point_id,from_point_code,to_point_id,to_point_code,stock_id,stock_code,pick_id,pick_detail_id,inventory_id,plan_qty,move_qty,task_type,task_status,iz_all,sys_org_code,tenant_id,create_by,create_time) " + - "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + String sql = "INSERT INTO data_task (id,task_no,item_id,item_code,from_point_id,from_point_code,to_point_id,to_point_code,stock_id,stock_code,pick_id,pick_detail_id,item_key_id,inventory_id,plan_qty,move_qty,task_type,task_status,iz_all,sys_org_code,tenant_id,create_by,create_time) " + + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; batchInsert(sql, tasks, (ps, task) -> { try { ps.setLong(1, IdWorker.getId()); @@ -302,16 +303,17 @@ public class BatchUtil { ps.setString(10, task.getStockCode()); ps.setLong(11, task.getPickId()); ps.setLong(12, task.getPickDetailId()); - ps.setLong(13, task.getInventoryId()); - ps.setBigDecimal(14, task.getPlanQty()); - ps.setBigDecimal(15, task.getMoveQty()); - ps.setInt(16, task.getTaskType()); - ps.setInt(17, task.getTaskStatus()); - ps.setInt(18, task.getIzAll()); - ps.setString(19, task.getSysOrgCode()); - ps.setLong(20, task.getTenantId()); - ps.setString(21, task.getCreateBy()); - ps.setTimestamp(22, new Timestamp(task.getCreateTime().getTime())); + ps.setLong(13, task.getItemKeyId()); + ps.setLong(14, task.getInventoryId()); + ps.setBigDecimal(15, task.getPlanQty()); + ps.setBigDecimal(16, task.getMoveQty()); + ps.setInt(17, task.getTaskType()); + ps.setInt(18, task.getTaskStatus()); + ps.setInt(19, task.getIzAll()); + ps.setString(20, task.getSysOrgCode()); + ps.setLong(21, task.getTenantId()); + ps.setString(22, task.getCreateBy()); + ps.setTimestamp(23, new Timestamp(task.getCreateTime().getTime())); } catch (SQLException e) { throw new RuntimeException(e); } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/CodeGeneratorUtil.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/CodeGeneratorUtil.java index 2038222..93efec8 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/CodeGeneratorUtil.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/CodeGeneratorUtil.java @@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.CannotAcquireLockException; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -13,7 +14,7 @@ import java.time.format.DateTimeFormatter; import java.util.List; @Slf4j -@Service +@Component public class CodeGeneratorUtil { @Autowired @@ -31,13 +32,8 @@ public class CodeGeneratorUtil { public String generateSerialNumber(String type) { try { String dateStr = LocalDate.now().format(DATE_FORMATTER); - - // 使用 SELECT for update 加锁查询并更新 - String lockAndUpdateSql = - "SELECT current_seq FROM generator_sequence WHERE type = ? AND date_str = ? for update"; - - List result = jdbcTemplate.queryForList(lockAndUpdateSql, Integer.class, type, dateStr); - + String sql = "SELECT current_seq FROM generator_sequence WHERE type = ? AND date_str = ? for update"; + List result = jdbcTemplate.queryForList(sql, Integer.class, type, dateStr); if (result.isEmpty()) { // 插入初始值 String insertSql = "INSERT INTO generator_sequence (type, date_str, current_seq) VALUES (?, ?, 1)"; @@ -53,7 +49,7 @@ public class CodeGeneratorUtil { return type + dateStr + seqStr; } } catch (CannotAcquireLockException e) { - throw new RuntimeException("系统繁忙,请稍后重试", e); + throw new RuntimeException("单号生成中,请稍后重试", e); } } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/RedisDistributedLockUtil.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/RedisDistributedLockUtil.java new file mode 100644 index 0000000..fc0ba2f --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/RedisDistributedLockUtil.java @@ -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 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 script = new DefaultRedisScript<>(luaScript, Long.class); + Long result = redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue); + + return result != null && result == 1; + } +} diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/SwmsLoginUtil.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/SwmsLoginUtil.java index d43b389..8136116 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/SwmsLoginUtil.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/SwmsLoginUtil.java @@ -6,18 +6,18 @@ import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.cpte.modules.constant.GeneralConstant; -import org.cpte.modules.saiWms.request.SaiWmsRequest; +import org.cpte.modules.saiWms.request.SMOMRequest; import org.jeecg.modules.openapi.entity.OpenApi; import org.jeecg.modules.openapi.mapper.OpenApiMapper; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j -@Service +@Component public class SwmsLoginUtil { @Autowired @@ -27,15 +27,15 @@ public class SwmsLoginUtil { String userName = "LM"; String password = "654321"; - SaiWmsRequest.ParameterValue3 parameterValue1 = new SaiWmsRequest.ParameterValue3(); + SMOMRequest.ParameterValue3 parameterValue1 = new SMOMRequest.ParameterValue3(); parameterValue1.setValue(userName); - SaiWmsRequest.ParameterValue3 parameterValue2 = new SaiWmsRequest.ParameterValue3(); + SMOMRequest.ParameterValue3 parameterValue2 = new SMOMRequest.ParameterValue3(); parameterValue2.setValue(password); - SaiWmsRequest.Context context = new SaiWmsRequest.Context(); + SMOMRequest.Context context = new SMOMRequest.Context(); - SaiWmsRequest saiWmsRequest = new SaiWmsRequest(); + SMOMRequest saiWmsRequest = new SMOMRequest(); saiWmsRequest.setApiType("AuthenticationController"); saiWmsRequest.setParameters(List.of(parameterValue1, parameterValue2)); saiWmsRequest.setMethod("Login"); diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/test.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/test.java index 5cb843d..7ba5447 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/test.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/test.java @@ -15,6 +15,10 @@ public class test { tesMap.put("ak", "ak-EMCNIpxwfMXzJ8rj"); tesMap.put("sk", "HtT14KlSwCfLfLyGe3FeJVPc3zmjZwXR"); + Map agvMap = new HashMap<>(); + agvMap.put("ak", "ak-EE2BXylYpsRcW9LY"); + agvMap.put("sk", "20xCnGWwgh6reNqmp5CbW7WjVBHVC9nN"); + long timestamp = System.currentTimeMillis(); System.out.println("sai-timestamp:" + timestamp); @@ -22,7 +26,9 @@ public class test { System.out.println("======================================================================================="); System.out.println("tes-timestamp:" + timestamp); System.out.println("tes-signature:" + md5(tesMap.get("ak")+ tesMap.get("sk") + timestamp)); - + System.out.println("======================================================================================="); + System.out.println("agv-timestamp:" + timestamp); + System.out.println("agv-signature:" + md5(agvMap.get("ak")+ agvMap.get("sk") + timestamp)); } public static String md5(String sourceStr) { diff --git a/cpte-module-system/cpte-system-biz/src/main/java/org/jeecg/modules/openapi/controller/OpenApiController.java b/cpte-module-system/cpte-system-biz/src/main/java/org/jeecg/modules/openapi/controller/OpenApiController.java index 6cdfa3b..9663635 100644 --- a/cpte-module-system/cpte-system-biz/src/main/java/org/jeecg/modules/openapi/controller/OpenApiController.java +++ b/cpte-module-system/cpte-system-biz/src/main/java/org/jeecg/modules/openapi/controller/OpenApiController.java @@ -7,6 +7,9 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; +import org.apache.poi.ss.formula.functions.T; +import org.jeecg.common.api.vo.HikResult; +import org.jeecg.common.api.vo.TesResult; import org.jeecg.modules.openapi.swagger.*; import org.jeecg.common.api.vo.Result; import org.jeecg.common.constant.CommonConstant; @@ -35,6 +38,7 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import jakarta.servlet.http.HttpServletRequest; + import java.net.URI; import java.util.*; import java.util.stream.Collectors; @@ -121,11 +125,11 @@ public class OpenApiController extends JeecgController */ @DeleteMapping(value = "/deleteBatch") public Result deleteBatch(@RequestParam(name = "ids", required = true) String ids) { - List list= Arrays.asList(ids.split(",")); - list.forEach(id->{ - OpenApi openApi = service.getById(id); - redisUtil.del(openApi.getRequestUrl()); - }); + List list = Arrays.asList(ids.split(",")); + list.forEach(id -> { + OpenApi openApi = service.getById(id); + redisUtil.del(openApi.getRequestUrl()); + }); this.service.removeByIds(Arrays.asList(ids.split(","))); return Result.ok("批量删除成功!"); } @@ -144,11 +148,12 @@ public class OpenApiController extends JeecgController /** * 接口调用 + * * @param path * @return */ - @RequestMapping(value = "/call/{path}", method = {RequestMethod.GET,RequestMethod.POST}) - public Result call(@PathVariable String path, @RequestBody(required = false) String json, HttpServletRequest request) { + @RequestMapping(value = "/call/{path}", method = {RequestMethod.GET, RequestMethod.POST}) + public Object call(@PathVariable String path, @RequestBody(required = false) String json, HttpServletRequest request) { OpenApi openApi = service.findByPath(path); if (Objects.isNull(openApi)) { Map result = new HashMap<>(); @@ -158,8 +163,8 @@ public class OpenApiController extends JeecgController } HttpHeaders httpHeaders = new HttpHeaders(); if (StrUtil.isNotEmpty(openApi.getHeadersJson())) { - List headers = JSON.parseArray(openApi.getHeadersJson(),OpenApiHeader.class); - if (headers.size()>0) { + List headers = JSON.parseArray(openApi.getHeadersJson(), OpenApiHeader.class); + if (headers.size() > 0) { for (OpenApiHeader header : headers) { httpHeaders.put(header.getHeaderKey(), Lists.newArrayList(request.getHeader(header.getHeaderKey()))); } @@ -173,9 +178,9 @@ public class OpenApiController extends JeecgController SysUser systemUser = sysUserService.getUserByName(openApiAuth.getCreateBy()); String token = this.getToken(systemUser.getUsername(), systemUser.getPassword()); httpHeaders.put("X-Access-Token", Lists.newArrayList(token)); - httpHeaders.put("Content-Type",Lists.newArrayList("application/json")); + httpHeaders.put("Content-Type", Lists.newArrayList("application/json")); HttpEntity httpEntity = new HttpEntity<>(json, httpHeaders); - url = RestUtil.getBaseUrl() + url; + url = RestUtil.getBaseUrl() + url; UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url); if (HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method) @@ -184,16 +189,16 @@ public class OpenApiController extends JeecgController //拼接参数 if (!request.getParameterMap().isEmpty()) { if (StrUtil.isNotEmpty(openApi.getParamsJson())) { - List params = JSON.parseArray(openApi.getParamsJson(),OpenApiParam.class); - if (params.size()>0) { + List params = JSON.parseArray(openApi.getParamsJson(), OpenApiParam.class); + if (params.size() > 0) { Map openApiParamMap = params.stream().collect(Collectors.toMap(p -> p.getParamKey(), p -> p, (e, r) -> e)); request.getParameterMap().forEach((k, v) -> { OpenApiParam openApiParam = openApiParamMap.get(k); if (Objects.nonNull(openApiParam)) { - if(v==null&&StrUtil.isNotEmpty(openApiParam.getDefaultValue())){ + if (v == null && StrUtil.isNotEmpty(openApiParam.getDefaultValue())) { builder.queryParam(openApiParam.getParamKey(), openApiParam.getDefaultValue()); } - if (v!=null){ + if (v != null) { builder.queryParam(openApiParam.getParamKey(), v); } } @@ -204,7 +209,17 @@ public class OpenApiController extends JeecgController } } 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 operation.setTags(Lists.newArrayList("openapi")); operation.setSummary(openApi.getName()); operation.setDescription(openApi.getName()); - operation.setOperationId(openApi.getRequestUrl()+"Using"+openApi.getRequestMethod()); + operation.setOperationId(openApi.getRequestUrl() + "Using" + openApi.getRequestMethod()); operation.setProduces(Lists.newArrayList("application/json")); parameters(operation, openApi); @@ -261,19 +276,19 @@ public class OpenApiController extends JeecgController definition.setType("object"); Map definitionProperties = new HashMap<>(); definition.setProperties(definitionProperties); - if (openApi.getBody()!=null){ + if (openApi.getBody() != null) { JSONObject jsonObject = JSONObject.parseObject(openApi.getBody()); - if (jsonObject.size()>0){ + if (jsonObject.size() > 0) { for (Map.Entry properties : jsonObject.entrySet()) { SwaggerDefinitionProperties swaggerDefinitionProperties = new SwaggerDefinitionProperties(); swaggerDefinitionProperties.setType("string"); - swaggerDefinitionProperties.setDescription(properties.getValue()+""); + swaggerDefinitionProperties.setDescription(properties.getValue() + ""); definitionProperties.put(properties.getKey(), swaggerDefinitionProperties); } } } // body的definition构建完成 - definitions.put(openApi.getRequestUrl()+"Using"+openApi.getRequestMethod()+"body", definition); + definitions.put(openApi.getRequestUrl() + "Using" + openApi.getRequestMethod() + "body", definition); SwaggerOperationParameter bodyParameter = new SwaggerOperationParameter(); bodyParameter.setDescription(openApi.getName() + " body"); @@ -282,7 +297,7 @@ public class OpenApiController extends JeecgController bodyParameter.setRequired(true); Map bodySchema = new HashMap<>(); - bodySchema.put("$ref", "#/definitions/" + openApi.getRequestUrl()+"Using"+openApi.getRequestMethod()+"body"); + bodySchema.put("$ref", "#/definitions/" + openApi.getRequestUrl() + "Using" + openApi.getRequestMethod() + "body"); bodyParameter.setSchema(bodySchema); // 构建参数构建完成 @@ -351,7 +366,7 @@ public class OpenApiController extends JeecgController operation.setResponses(responses); operations.put(openApi.getRequestMethod().toLowerCase(), operation); - paths.put("/openapi/call/"+openApi.getRequestUrl(), operations); + paths.put("/openapi/call/" + openApi.getRequestUrl(), operations); } swaggerModel.setDefinitions(definitions); @@ -361,7 +376,7 @@ public class OpenApiController extends JeecgController private void parameters(SwaggerOperation operation, OpenApi openApi) { List parameters = new ArrayList<>(); - if (openApi.getParamsJson()!=null) { + if (openApi.getParamsJson() != null) { List openApiParams = JSON.parseArray(openApi.getParamsJson(), OpenApiParam.class); for (OpenApiParam openApiParam : openApiParams) { SwaggerOperationParameter parameter = new SwaggerOperationParameter(); @@ -372,7 +387,7 @@ public class OpenApiController extends JeecgController parameters.add(parameter); } } - if (openApi.getHeadersJson()!=null) { + if (openApi.getHeadersJson() != null) { List openApiHeaders = JSON.parseArray(openApi.getHeadersJson(), OpenApiHeader.class); for (OpenApiHeader openApiHeader : openApiHeaders) { SwaggerOperationParameter parameter = new SwaggerOperationParameter(); @@ -410,6 +425,7 @@ public class OpenApiController extends JeecgController /** * 生成接口路径 + * * @return */ @GetMapping("genPath")