diff --git a/cpte-boot-base-core/src/main/java/org/jeecg/common/api/dto/LogDTO.java b/cpte-boot-base-core/src/main/java/org/jeecg/common/api/dto/LogDTO.java index f301058..6b67f30 100644 --- a/cpte-boot-base-core/src/main/java/org/jeecg/common/api/dto/LogDTO.java +++ b/cpte-boot-base-core/src/main/java/org/jeecg/common/api/dto/LogDTO.java @@ -17,6 +17,9 @@ public class LogDTO implements Serializable { /**内容*/ private String logContent; + /** 返回 */ + private String returnData; + /**日志类型(0:操作日志;1:登录日志;2:定时任务) */ private Integer logType; diff --git a/cpte-boot-base-core/src/main/java/org/jeecg/common/aspect/AutoLogAspect.java b/cpte-boot-base-core/src/main/java/org/jeecg/common/aspect/AutoLogAspect.java index 7befb15..ddb7942 100644 --- a/cpte-boot-base-core/src/main/java/org/jeecg/common/aspect/AutoLogAspect.java +++ b/cpte-boot-base-core/src/main/java/org/jeecg/common/aspect/AutoLogAspect.java @@ -1,5 +1,6 @@ package org.jeecg.common.aspect; +import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.PropertyFilter; import org.apache.shiro.SecurityUtils; @@ -28,6 +29,7 @@ import jakarta.annotation.Resource; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; + import java.lang.reflect.Method; import java.util.Date; @@ -71,10 +73,10 @@ public class AutoLogAspect { LogDTO dto = new LogDTO(); AutoLog syslog = method.getAnnotation(AutoLog.class); - if(syslog != null){ + if (syslog != null) { //update-begin-author:taoyan date: String content = syslog.value(); - if(syslog.module()== ModuleType.ONLINE){ + if (syslog.module() == ModuleType.ONLINE) { content = getOnlineLogContent(obj, content); } //注解上的描述,操作日志内容 @@ -95,13 +97,16 @@ public class AutoLogAspect { //获取request HttpServletRequest request = SpringContextUtils.getHttpServletRequest(); + //请求的url + dto.setRequestUrl(request.getRequestURL().toString()); //请求的参数 - dto.setRequestParam(getReqestParams(request,joinPoint)); + dto.setRequestParam(getReqestParams(request, joinPoint)); + dto.setReturnData(handlerResult(obj)); //设置IP地址 dto.setIp(IpUtils.getIpAddr(request)); //获取登录用户信息 LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal(); - if(sysUser!=null){ + if (sysUser != null) { dto.setUserid(sysUser.getUsername()); dto.setUsername(sysUser.getRealname()); } @@ -116,7 +121,7 @@ public class AutoLogAspect { /** * 获取操作类型 */ - private int getOperateType(String methodName,int operateType) { + private int getOperateType(String methodName, int operateType) { if (operateType > 0) { return operateType; } @@ -126,11 +131,11 @@ public class AutoLogAspect { } /** + * @param request: request + * @param joinPoint: joinPoint * @Description: 获取请求参数 * @author: scott * @date: 2020/4/16 0:10 - * @param request: request - * @param joinPoint: joinPoint * @Return: java.lang.String */ private String getReqestParams(HttpServletRequest request, JoinPoint joinPoint) { @@ -140,7 +145,7 @@ public class AutoLogAspect { Object[] paramsArray = joinPoint.getArgs(); // java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false) // https://my.oschina.net/mengzhang6/blog/2395893 - Object[] arguments = new Object[paramsArray.length]; + Object[] arguments = new Object[paramsArray.length]; for (int i = 0; i < paramsArray.length; i++) { if (paramsArray[i] instanceof BindingResult || paramsArray[i] instanceof ServletRequest || paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile) { //ServletRequest不能序列化,从入参里排除,否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false) @@ -154,7 +159,7 @@ public class AutoLogAspect { @Override public boolean apply(Object o, String name, Object value) { int length = 500; - if(value!=null && value.toString().length()>length){ + if (value != null && value.toString().length() > length) { return false; } return !(value instanceof MultipartFile); @@ -168,7 +173,7 @@ public class AutoLogAspect { // 请求的方法参数值 Object[] args = joinPoint.getArgs(); // 请求的方法参数名称 - StandardReflectionParameterNameDiscoverer u=new StandardReflectionParameterNameDiscoverer(); + StandardReflectionParameterNameDiscoverer u = new StandardReflectionParameterNameDiscoverer(); String[] paramNames = u.getParameterNames(method); if (args != null && paramNames != null) { for (int i = 0; i < args.length; i++) { @@ -179,24 +184,46 @@ public class AutoLogAspect { return params; } + private String handlerResult(Object result) { + if (result == null) { + return null; + } + String resultStr; + try { + if (result instanceof String) { + resultStr = (String) result; + } else { + resultStr = JSON.toJSONString(result);// 如果返回结果非String类型,转换成JSON格式的字符串 + } + + if (resultStr.length() > 10000) { + resultStr = resultStr.substring(0, 10000); + } + } catch (Exception e) { + resultStr = result.toString(); + } + return resultStr; + } + /** * online日志内容拼接 + * * @param obj * @param content * @return */ - private String getOnlineLogContent(Object obj, String content){ - if (obj instanceof Result){ - Result res = (Result)obj; + private String getOnlineLogContent(Object obj, String content) { + if (obj instanceof Result) { + Result res = (Result) obj; String msg = res.getMessage(); String tableName = res.getOnlTable(); - if(oConvertUtils.isNotEmpty(tableName)){ - content+=",表名:"+tableName; + if (oConvertUtils.isNotEmpty(tableName)) { + content += ",表名:" + tableName; } - if(res.isSuccess()){ - content+= ","+(oConvertUtils.isEmpty(msg)?"操作成功":msg); - }else{ - content+= ","+(oConvertUtils.isEmpty(msg)?"操作失败":msg); + if (res.isSuccess()) { + content += "," + (oConvertUtils.isEmpty(msg) ? "操作成功" : msg); + } else { + content += "," + (oConvertUtils.isEmpty(msg) ? "操作失败" : msg); } } return content; diff --git a/cpte-boot-base-core/src/main/java/org/jeecg/modules/base/mapper/xml/BaseCommonMapper.xml b/cpte-boot-base-core/src/main/java/org/jeecg/modules/base/mapper/xml/BaseCommonMapper.xml index 8f5d80c..1b02fe1 100644 --- a/cpte-boot-base-core/src/main/java/org/jeecg/modules/base/mapper/xml/BaseCommonMapper.xml +++ b/cpte-boot-base-core/src/main/java/org/jeecg/modules/base/mapper/xml/BaseCommonMapper.xml @@ -4,7 +4,7 @@ - insert into sys_log (id, log_type, log_content, method, operate_type, request_url, request_type, request_param, ip, userid, username, cost_time, create_time,create_by, tenant_id, client_type) + insert into sys_log (id, log_type, log_content, method, operate_type, request_url, request_type, request_param,return_data, ip, userid, username, cost_time, create_time,create_by, tenant_id, client_type) values( #{dto.id,jdbcType=VARCHAR}, #{dto.logType,jdbcType=INTEGER}, @@ -14,6 +14,7 @@ #{dto.requestUrl,jdbcType=VARCHAR}, #{dto.requestType,jdbcType=VARCHAR}, #{dto.requestParam,jdbcType=VARCHAR}, + #{dto.returnData,jdbcType=VARCHAR}, #{dto.ip,jdbcType=VARCHAR}, #{dto.userid,jdbcType=VARCHAR}, #{dto.username,jdbcType=VARCHAR}, diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/mapper/PointMapper.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/mapper/PointMapper.java index 87a896a..0b99f78 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/mapper/PointMapper.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/mapper/PointMapper.java @@ -34,14 +34,24 @@ public interface PointMapper extends BaseMapper { List queryPoints(@Param("pointCode") String pointCode, @Param("status") Integer status, @Param("areaCode") String areaCode); /** - * 查询同SKU、批次、PO的库位 + * 查询库位信息 * - * @param itemId 物料ID - * @param propC1 批次号 - * @param whCode 仓库编码 + * @param itemId 商品ID + * @param whCode 仓库编号 + * @param projects 项目编号 + * @param taskNos 任务编号 + * @param propC1List 批次号 + * @param propC3List 外部库存状态 + * @param areaCode 库区编号 * @return List */ - List findPointsWithSkuBatchPo(@Param("itemId") Long itemId, @Param("propC1") String propC1, @Param("whCode") String whCode,@Param("areaCode") String areaCode); + List findPointsWithSkuBatchPo(@Param("itemId") Long itemId, + @Param("whCode") String whCode, + @Param("projects") List projects, + @Param("taskNos") List taskNos, + @Param("propC1List") List propC1List, + @Param("propC3List") List propC3List, + @Param("areaCode") String areaCode); // 查询同一巷道占用数量 @Select("SELECT COUNT(id) FROM base_point WHERE col_num = #{colNum} AND layer_num = #{layerNum} AND status = 1") diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/mapper/xml/PointMapper.xml b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/mapper/xml/PointMapper.xml index dc69030..e776c63 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/mapper/xml/PointMapper.xml +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/mapper/xml/PointMapper.xml @@ -25,14 +25,6 @@ WHERE inv.item_id = #{itemId} AND bp1.status = 0 AND ba.area_code = #{areaCode} - - - AND inv.prop_c1 = #{propC1} - - - AND (inv.prop_c1 IS NULL OR inv.prop_c1 = '') - - AND inv.wh_code = #{whCode} @@ -41,5 +33,49 @@ AND (inv.wh_code IS NULL OR inv.wh_code = '') + + + AND inv.project IN + + #{project} + + + + AND (inv.project IS NULL OR inv.project = '') + + + + + AND inv.task_no IN + + #{taskNo} + + + + AND (inv.task_no IS NULL OR inv.task_no = '') + + + + + AND inv.prop_c1 IN + + #{propC1} + + + + AND (inv.prop_c1 IS NULL OR inv.prop_c1 = '') + + + + + AND inv.prop_c3 IN + + #{propC3} + + + + AND (inv.prop_c3 IS NULL OR inv.prop_c3 = '') + + \ No newline at end of file diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/service/IPointService.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/service/IPointService.java index 4951bc3..e81f622 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/service/IPointService.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/service/IPointService.java @@ -3,6 +3,8 @@ package org.cpte.modules.base.service; import org.apache.ibatis.annotations.Param; import org.cpte.modules.base.entity.Point; import com.baomidou.mybatisplus.extension.service.IService; +import org.cpte.modules.receive.entity.Asn; +import org.cpte.modules.receive.entity.AsnDetail; import java.util.List; import java.util.Map; @@ -97,4 +99,19 @@ public interface IPointService extends IService { * @return List */ List queryPoints(String pointCode, Integer status, String areaCode); + + /** + * 查询物料聚集库位 + * + * @param itemId 物料 + * @param whCode 仓库代码 + * @param projects 项目号 + * @param taskNos 任务号 + * @param propC1List 批次 + * @param propC3List 外部库存状态 + * @param areaCode 库区编码 + * @return List + */ + List findClusterPoint(Long itemId, String whCode, List projects, List taskNos, List propC1List, List propC3List, String areaCode); + } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/service/impl/PointServiceImpl.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/service/impl/PointServiceImpl.java index e897236..a32d5ba 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/service/impl/PointServiceImpl.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/service/impl/PointServiceImpl.java @@ -153,4 +153,9 @@ public class PointServiceImpl extends ServiceImpl implements return pointMapper.queryPoints(pointCode, status, areaCode); } + @Override + public List findClusterPoint(Long itemId, String whCode, List projects, List taskNos, List propC1List, List propC3List, String areaCode) { + return pointMapper.findPointsWithSkuBatchPo(itemId, whCode,projects,taskNos, propC1List, propC3List, areaCode); + } + } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/service/impl/StockServiceImpl.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/service/impl/StockServiceImpl.java index ff31eb2..098d759 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/service/impl/StockServiceImpl.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/base/service/impl/StockServiceImpl.java @@ -63,7 +63,7 @@ public class StockServiceImpl extends ServiceImpl implements @Override public void bindStock(Stock stock, Point point) { - stock.setPointId(point.getId()); + stock.setPointId(point == null ? null : point.getId()); stock.setStatus(CommonStatusEnum.USED.getValue()); stockMapper.updateById(stock); } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/constant/GeneralConstant.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/constant/GeneralConstant.java index d9306f4..746a691 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/constant/GeneralConstant.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/constant/GeneralConstant.java @@ -55,6 +55,11 @@ public interface GeneralConstant { */ String PICK_ORDER_NO = "pick_order_no"; + /** + * 移位规则编码 + */ + String MOVE_ORDER_NO = "move_order_no"; + /** * TES任务下发接口 */ diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/constant/enums/TaskTypeEnum.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/constant/enums/TaskTypeEnum.java index accc8f4..7cc5636 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/constant/enums/TaskTypeEnum.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/constant/enums/TaskTypeEnum.java @@ -5,6 +5,7 @@ import lombok.Getter; @Getter public enum TaskTypeEnum { PICK(1, "出库"), + MOVE(2, "移位"), ; TaskTypeEnum(Integer value, String desc) { 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 dabded2..ba0b992 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 @@ -1,8 +1,6 @@ package org.cpte.modules.conveyorLine.service.impl; import com.alibaba.fastjson.JSONObject; -import lombok.AllArgsConstructor; -import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -24,6 +22,7 @@ import org.cpte.modules.receive.entity.Asn; import org.cpte.modules.receive.entity.AsnDetail; import org.cpte.modules.receive.mapper.AsnDetailMapper; import org.cpte.modules.receive.mapper.AsnMapper; +import org.cpte.modules.shipping.entity.PickDetail; import org.cpte.modules.shipping.entity.Task; import org.cpte.modules.shipping.mapper.TaskMapper; import org.springframework.beans.factory.annotation.Autowired; @@ -81,17 +80,18 @@ public class IConveyorLineServiceImpl implements IConveyorLineService { validateParams(scanTrayRequest); //工作站 - Point srcPoint = pointMapper.queryByPointCode(scanTrayRequest.getStation()); + Point station = pointMapper.queryByPointCode(scanTrayRequest.getStation()); //容器 Stock stock = stockMapper.queryByStockCode(scanTrayRequest.getStockCode()); //验证入库信息 - AsnDetail asnDetail = asnDetailMapper.queryByStockCode(stock.getId(), AsnStatusEnum.CREATED.getValue()); - if (asnDetail == null) { - throw new RuntimeException("【" + scanTrayRequest.getStockCode() + "】托盘,无入库信息"); + List asnDetails = asnDetailMapper.queryByStockCode(stock.getId(), AsnStatusEnum.CREATED.getValue()); + if (CollectionUtils.isEmpty(asnDetails)) { + throw new RuntimeException("【" + scanTrayRequest.getStockCode() + "】托盘,无入库任务"); } - Asn asn = asnMapper.selectById(asnDetail.getAsnId()); + Asn asn = asnMapper.selectById(asnDetails.get(0).getAsnId()); + AsnDetail asnDetail = asnDetails.get(0); //验证托盘是否有库存 if (inventoryMapper.queryByStockId(stock.getId()) != null) { throw new RuntimeException("【" + scanTrayRequest.getStockCode() + "】托盘已入库"); @@ -102,39 +102,53 @@ public class IConveyorLineServiceImpl implements IConveyorLineService { throw new RuntimeException("【" + scanTrayRequest.getStockCode() + "】托盘已扫描,请勿重复扫描"); } + //项目号、任务号、批次号、外部库存状态 + List projects = asnDetails.stream().map(AsnDetail::getProject).filter(StringUtils::isNotBlank).distinct().toList(); + List taskNos = 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(); + + //通过算法获取目标点位 - Point dstPoint = allocatePoint(asn, asnDetail, srcPoint); + Point dstPoint = allocatePoint(asn.getOrderType(), asnDetail.getItemId(), asn.getWhCode(), projects, taskNos, propC1List, propC3List, station); //锁定目标库位 pointService.bindPoint(dstPoint); //验证通过,生成Tes任务 - iAgvTaskService.createAgvTask(asnDetail.getId(), stock.getStockCode(), srcPoint.getPointCode(), dstPoint.getPointCode(), null, BusinessTypeEnum.INBOUND.getValue(), 0, AgvVendorEnum.TES.getValue()); + iAgvTaskService.createAgvTask(asn.getId(), stock.getStockCode(), station.getPointCode(), dstPoint.getPointCode(), null, BusinessTypeEnum.INBOUND.getValue(), 0, AgvVendorEnum.TES.getValue()); } /** * 智能库位分配库位 * - * @param asnDetail 入库明细 + * @param orderType 单据类型 + * @param itemId 物料 + * @param whCode 仓库代码 + * @param projects 项目号 + * @param taskNos 任务号 + * @param propC1List 批次号 + * @param propC3List 外部库存状态 + * @param station 工作站 * @return 目标库位 */ - private Point allocatePoint(Asn asn, AsnDetail asnDetail, Point station) { + private Point allocatePoint(Integer orderType, Long itemId, String whCode, List projects, List taskNos, List propC1List, List propC3List, Point station) { String areaCode = ""; - if (Set.of(0, 1, 2, 3).contains(asn.getOrderType())) { + if (Set.of(0, 1, 2, 3).contains(orderType)) { areaCode = AreaTypeEnum.CPCCQ.getValue(); } else { areaCode = AreaTypeEnum.MJCCQ.getValue(); } - //1.优先寻找同物料/同批次/同PO聚集库位 - List availablePoints = findClusterPoint(asn, asnDetail, areaCode); + //1.优先寻找同物料/同仓库/同项目号/同任务号/同批次/同外部库存状态 + List availablePoints = pointService.findClusterPoint(itemId, whCode, projects, taskNos, propC1List, propC3List, areaCode); if (CollectionUtils.isEmpty(availablePoints)) { //2.获取所有可用库位 availablePoints = pointMapper.queryPoints(null, CommonStatusEnum.FREE.getValue(), areaCode); } List scoredPoints = availablePoints.stream() - .map(point -> calculateEnhancedLocationScore(point, station, asn, asnDetail)) + .map(point -> clusterPointScore(point, station, itemId, whCode, projects, taskNos, propC1List, propC3List)) .sorted(Comparator.comparing(PointScore::getScore).reversed()) .toList(); @@ -149,42 +163,30 @@ public class IConveyorLineServiceImpl implements IConveyorLineService { * 寻找物料聚集库位 * */ - private List findClusterPoint(Asn asn, AsnDetail asnDetail, String areaCode) { - return pointMapper.findPointsWithSkuBatchPo(asnDetail.getItemId(), asnDetail.getPropC1(), asn.getWhCode(), areaCode); - } /** - * 增强版库位评分计算 - * 新增:通道类型深度策略 + 物料聚集加分 + * 库位评分计算 */ - private PointScore calculateEnhancedLocationScore(Point point, Point station, Asn asn, AsnDetail asnDetail) { - double totalScore = 0.0; + private PointScore clusterPointScore(Point point, Point station, Long itemId, String whCode, List projects, List taskNos, List propC1List, List propC3List) { + double totalScore; // 1. 距离评分 - 考虑从入库口到库位的距离 - double distanceScore = calculateClusterDistanceCost(point, station); - totalScore += distanceScore * 0.3; // 权重从40%降到30% - log.info("【{}】距离评分: {}", point.getPointCode(), totalScore); + double distanceScore = calculateClusterDistanceCost(point, station) * 0.3;// 权重30% // 2. 通道深度策略评分 - 新增核心策略 - double channelDepthScore = calculateChannelDepthScore(point); - totalScore += channelDepthScore * 0.25; // 新增25%权重 - log.info("【{}】通道深度评分: {}", point.getPointCode(), totalScore); + double channelDepthScore = calculateChannelDepthScore(point) * 0.25; // 权重25% // 3. 通道类型评分 - 双通道优先 - double channelScore = calculateChannelScore(point); - totalScore += channelScore * 0.15; // 权重从20%降到15% - log.info("【{}】通道类型评分: {}", point.getPointCode(), totalScore); + double channelScore = calculateChannelScore(point) * 0.15; // 权重15% // 4. 均衡评分 - 避免热点区域 - double balanceScore = calculateBalanceScore(point); - totalScore += balanceScore * 0.1; // 权重从15%降到10% - log.info("【{}】均衡评分: {}", point.getPointCode(), totalScore); + double balanceScore = calculateBalanceScore(point) * 0.1; // 权重10% // 5. 物料聚集潜力评分 - 新增 - double clusterPotentialScore = calculateClusterPotentialScore(point, asn, asnDetail); - totalScore += clusterPotentialScore * 0.2; // 新增20%权重 - log.info("【{}】物料聚集潜力评分: {}", point.getPointCode(), totalScore); + double clusterPotentialScore = calculateClusterPotentialScore(point, itemId, whCode, projects, taskNos, propC1List, propC3List) * 0.2; // 权重20% + totalScore = distanceScore + channelDepthScore + channelScore + balanceScore + clusterPotentialScore; + log.info("【{}】库位总分:{} - 距离评分: {} - 通道深度策略评分: {} - 通道类型评分: {} - 均衡评分: {} - 物料聚集评分: {}", point.getPointCode(), totalScore, distanceScore, channelDepthScore, channelScore, balanceScore, clusterPotentialScore); return new PointScore(point, totalScore); } @@ -250,38 +252,66 @@ public class IConveyorLineServiceImpl implements IConveyorLineService { /** * 计算物料聚集潜力评分 * 检查周围库位是否有相同特征的货物 + * 新增货物特性评分:物料、仓库代码、项目号、任务号、批次号、外部库存状态 */ - private double calculateClusterPotentialScore(Point point, Asn asn, AsnDetail asnDetail) { + private double calculateClusterPotentialScore(Point point, Long itemId, String whCode, + List projects, List taskNos, + List propC1List, + List propC3List) { 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; // 到最深位置 + minDepth = Integer.parseInt(point.getRowNum()); + maxDepth = 7; } - List neighbors = inventoryMapper.findNeighborPoints(point.getColNum(), point.getLayerNum(), String.valueOf(minDepth), String.valueOf(maxDepth)); + List neighbors = inventoryMapper.findNeighborPoints( + point.getColNum(), point.getLayerNum(), String.valueOf(minDepth), String.valueOf(maxDepth)); for (Inventory neighbor : neighbors) { if (neighbor.getItemId() != null) { // 同SKU加分 - if (neighbor.getItemId().equals(asnDetail.getItemId())) { - score += 15; - // 同批次额外加分 - if (asnDetail.getPropC1() != null && asnDetail.getPropC1().equals(neighbor.getPropC1())) { + if (neighbor.getItemId().equals(itemId)) { + score += 20; + + // 同仓库代码加分 + if (whCode != null && whCode.equals(neighbor.getWhCode())) { score += 10; - // 同PO额外加分 - if (asn.getWhCode() != null && asn.getWhCode().equals(neighbor.getWhCode())) { - score += 5; - } + } + + // 同批次号加分 + if (CollectionUtils.isNotEmpty(propC1List) && + StringUtils.isNotBlank(neighbor.getPropC1()) && + propC1List.contains(neighbor.getPropC1())) { + score += 15; + } + + // 同项目号加分 + if (CollectionUtils.isNotEmpty(projects) && + StringUtils.isNotBlank(neighbor.getProject()) && + projects.contains(neighbor.getProject())) { + score += 10; + } + + // 同任务号加分 + if (CollectionUtils.isNotEmpty(taskNos) && + StringUtils.isNotBlank(neighbor.getTaskNo()) && + taskNos.contains(neighbor.getTaskNo())) { + score += 10; + } + + // 同外部库存状态加分 + if (CollectionUtils.isNotEmpty(propC3List) && + StringUtils.isNotBlank(neighbor.getPropC3()) && + propC3List.contains(neighbor.getPropC3())) { + score += 5; } } } @@ -291,6 +321,7 @@ public class IConveyorLineServiceImpl implements IConveyorLineService { return Math.min(score, 100); } + /** * 计算距离评分 * 基于入库口位置和库位坐标计算最短路径距离 diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/entity/Inventory.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/entity/Inventory.java index cd825c5..776b836 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/entity/Inventory.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/entity/Inventory.java @@ -86,30 +86,46 @@ public class Inventory implements Serializable { @Schema(description = "入库记录ID") @JsonSerialize(using = ToStringSerializer.class) private java.lang.Long receiveRecordId; + /** * 外部仓库 */ @Excel(name = "外部仓库", width = 15) @Schema(description = "外部仓库") private java.lang.String whCode; + + /** + * 项目号 + */ + @Schema(description = "项目号") + private java.lang.String project; + /** + * 任务号 + */ + @Schema(description = "任务号") + private java.lang.String taskNo; + /** * 批次号 */ @Excel(name = "批次号", width = 15) @Schema(description = "批次号") private java.lang.String propC1; - /** - * 序列号 - */ - @Excel(name = "序列号", width = 15) - @Schema(description = "序列号") - private java.lang.String propC2; + /** * 外部库存状态 */ @Excel(name = "外部库存状态", width = 15) @Schema(description = "外部库存状态") private java.lang.String propC3; + + /** + * 序列号 + */ + @Excel(name = "序列号", width = 15) + @Schema(description = "序列号") + private java.lang.String propC2; + /** * 库存状态 */ 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 8ad558b..bcc5139 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 @@ -29,13 +29,13 @@ public interface InventoryMapper extends BaseMapper { /** * 查询库存列表 * - * @param itemIds 物料ID - * @param propC1List 批次号 - * @param propC3List 外部库存状态 - * @param whCodeList 外部仓库 + * @param itemIds 物料ID + * @param propC1List 批次号 + * @param propC3List 外部库存状态 + * @param whCodeList 外部仓库 * @return List */ - List queryInventory(@Param("itemIds") List itemIds, @Param("propC1List") List propC1List, @Param("propC3List") List propC3List, @Param("whCodeList") List whCodeList); + List queryInventory(@Param("itemIds") List itemIds, @Param("propC1List") List propC1List, @Param("propC3List") List propC3List, @Param("whCodeList") List whCodeList); // 查询相邻库位(同一巷道,深度±3范围内) @Select("SELECT di.* " + @@ -46,10 +46,16 @@ public interface InventoryMapper extends BaseMapper { " AND bp.row_num BETWEEN #{minDepth} AND #{maxDepth} " + " AND bp.status = 1") List findNeighborPoints(@Param("colNum") String colNum, - @Param("layerNum") String layerNum, - @Param("minDepth") String minDepth, - @Param("maxDepth") String maxDepth); - + @Param("layerNum") String layerNum, + @Param("minDepth") String minDepth, + @Param("maxDepth") String maxDepth); + /** + * 根据库位ID集合查询 + * + * @param pointIds 库位ID集合 + * @return List + */ + List queryByPointIds(@Param("pointIds") List pointIds); } diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/mapper/xml/InventoryMapper.xml b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/mapper/xml/InventoryMapper.xml index f18c0c4..9a1ce6e 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/mapper/xml/InventoryMapper.xml +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/mapper/xml/InventoryMapper.xml @@ -46,4 +46,13 @@ ORDER BY create_time + + \ No newline at end of file diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/service/IInventoryService.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/service/IInventoryService.java index b87a7c6..d85fe17 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/service/IInventoryService.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/service/IInventoryService.java @@ -23,11 +23,10 @@ public interface IInventoryService extends IService { * @param stockId 容器 * @param receivedQty 收货数量 * @param asn 入库单 - * @param asnDetail 入库明细 * @param receiveRecord 入库记录 * @return Inventory */ - Inventory createInventory(Long stockId, BigDecimal receivedQty, Asn asn, AsnDetail asnDetail, ReceiveRecord receiveRecord); + Inventory createInventory(Long stockId, BigDecimal receivedQty, Asn asn, ReceiveRecord receiveRecord); /** * 根据库存ID查询库存 diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/service/impl/InventoryServiceImpl.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/service/impl/InventoryServiceImpl.java index 95cf3e5..7c8e86b 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/service/impl/InventoryServiceImpl.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/inventory/service/impl/InventoryServiceImpl.java @@ -37,24 +37,26 @@ public class InventoryServiceImpl extends ServiceImpl { * @return AsnDetail */ @Select("select * from data_asn_detail where stock_id = #{stockId} and status = #{status} for update") - AsnDetail queryByStockCode(@Param("stockId") Long stockId, @Param("status") Integer status); + List queryByStockCode(@Param("stockId") Long stockId, @Param("status") Integer status); @Select("select MAX(line_no) from data_asn_detail where asn_id = #{asnId} ") Integer queryMaxLineNoByAsnId(@Param("asnId") Long asnId); 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 aac4dc7..eb4476a 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 @@ -50,10 +50,10 @@ public interface IAsnService extends IService { /** * 收货操作 * - * @param asnDetailId 入库明细ID + * @param asnId 入库单ID * @param pointCode 目标库位 */ - void receiveGoods(Long asnDetailId, String pointCode); + void receiveGoods(Long asnId, String pointCode); /** * 入库任务回传 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 aa8610d..fa0df84 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 @@ -93,10 +93,6 @@ public class AsnServiceImpl extends ServiceImpl implements IAsnS throw new RuntimeException("请新增入库明细"); } - if (asnDetailList.size() > 1) { - throw new RuntimeException("入库明细只允许新增一条"); - } - AtomicInteger lineNoCounter = new AtomicInteger(1); for (AsnDetail entity : asnDetailList) { if (entity.getLineNo() == null || entity.getLineNo() == 0) { @@ -207,57 +203,65 @@ public class AsnServiceImpl extends ServiceImpl implements IAsnS @Override @Transactional(rollbackFor = Exception.class) - public void receiveGoods(Long asnDetailId, String pointCode) { - //入库明细任务 - AsnDetail asnDetail = asnDetailMapper.selectById(asnDetailId); - if (asnDetail == null) { - throw new RuntimeException("未匹配到入库任务【" + asnDetailId + "】"); + public void receiveGoods(Long asnId, String pointCode) { + //入库任务 + Asn asn = asnMapper.selectById(asnId); + if (asn == null) { + throw new RuntimeException("未匹配到入库任务【" + asnId + "】"); } - + if (asn.getStatus() != 1) { + throw new RuntimeException("入库任务【" + asnId + "】已收货"); + } + List asnDetails = asnDetailMapper.selectByMainId(asnId); //验证容器是否入库 - if (inventoryMapper.queryByStockId(asnDetail.getStockId()) != null) { - throw new RuntimeException("【" + asnDetail.getStockId() + "】容器已入库"); + Stock stock = stockMapper.selectById(asnDetails.get(0).getStockId()); + if (inventoryMapper.queryByStockId(stock.getId()) != null) { + throw new RuntimeException("【" + stock.getStockCode() + "】容器已入库"); } - //入库单 - Asn asn = this.getById(asnDetail.getAsnId()); - //实际的存储位置 Point dstPoint = pointMapper.queryByPointCode(pointCode); - //容器 - Stock stock = stockMapper.selectById(asnDetail.getStockId()); - //更新收货数量 - BigDecimal receivedQty = BigDecimalUtil.add(asnDetail.getReceivedQty(), asnDetail.getOrderQty(), 0); - asnDetail.setReceivedQty(receivedQty); + for (AsnDetail ad : asnDetails) { + //已收货直接跳过 + if (AsnStatusEnum.RECEIVED.getValue().equals(ad.getStatus())) { + continue; + } + + BigDecimal receivedQty = BigDecimalUtil.add(ad.getReceivedQty(), ad.getOrderQty(), 0); + ad.setReceivedQty(receivedQty); + + //更新明细状态 + if (receivedQty.compareTo(ad.getOrderQty()) >= 0) { + ad.setStatus(AsnStatusEnum.RECEIVED.getValue()); + } else { + ad.setStatus(AsnStatusEnum.RECEIVING.getValue()); + } + asnDetailMapper.updateById(ad); + + //生成入库记录 + ReceiveRecord receiveRecord = createReceiveRecord(ad, receivedQty, dstPoint.getId()); + + // 生成库存 + Inventory inventory = iInventoryService.createInventory(stock.getId(), receivedQty, asn, receiveRecord); + + //添加库存日志 + iInventoryLogService.addInboundInventoryLog(inventory, ad.getToPointId(), receivedQty, asn.getOrderNo(), ad.getId(), ad.getDescription()); - //更新明细状态 - if (receivedQty.compareTo(asnDetail.getOrderQty()) >= 0) { - asnDetail.setStatus(AsnStatusEnum.RECEIVED.getValue()); - } else { - asnDetail.setStatus(AsnStatusEnum.RECEIVING.getValue()); } - asnDetailMapper.updateById(asnDetail); - //生成入库记录 - ReceiveRecord receiveRecord = createReceiveRecord(asnDetail, receivedQty, dstPoint.getId()); - - // 生成库存 - Inventory inventory = iInventoryService.createInventory(stock.getId(), receivedQty, asn, asnDetail, receiveRecord); //更新入库单 - refreshAsn(asn, asnDetailMapper.selectByMainId(asn.getId())); + refreshAsn(asn, asnDetails); //更新容器状态和位置 iStockService.bindStock(stock, dstPoint); iPointService.bindPoint(dstPoint); - //添加库存日志 - iInventoryLogService.addInboundInventoryLog(inventory, asnDetail.getToPointId(), receivedQty, asn.getOrderNo(), asnDetail.getId(), asnDetail.getDescription()); //回传 - receiveCallback(asn, asnDetail, stock); + // receiveCallback(asn, asnDetail, stock); } @@ -366,6 +370,8 @@ public class AsnServiceImpl extends ServiceImpl implements IAsnS .toPointId(dstPointId) .itemId(asnDetail.getItemId()) .receivedQty(receivedQty) + .project(asnDetail.getProject()) + .taskNo(asnDetail.getTaskNo()) .propC1(asnDetail.getPropC1()) .propC2(asnDetail.getPropC2()) .propC3(asnDetail.getPropC3()) 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 index fbc2f0b..f6061b5 100644 --- 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 @@ -87,9 +87,9 @@ public class ISaiWmsServiceImpl implements ISaiWmsService { if (inboundRequest.getType() == null) { throw new RuntimeException("任务类型(Type)必填"); } - if (inboundRequest.getType().equals(0)) { + if (AsnOrderTypeEnum.PRODUCT.getValue().equals(inboundRequest.getType())) { if (StringUtils.isBlank(inboundRequest.getLocationFrom())) { - throw new RuntimeException("成品入库,起点(LocationFrom)必填"); + throw new RuntimeException("起点(LocationFrom)必填"); } } @@ -120,6 +120,18 @@ public class ISaiWmsServiceImpl implements ISaiWmsService { 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("明细中物料只能是同一种"); + } + } @@ -136,16 +148,30 @@ public class ISaiWmsServiceImpl implements ISaiWmsService { throw new RuntimeException("【" + no + "】任务号已接收,请勿重复下发"); } - // 获取唯一的明细 - InboundRequest.InboundDetail detail = inboundRequest.getDetails().get(0); + // 获取明细 + List detail = inboundRequest.getDetails(); - // 验证基础数据 - String srcPointCode = inboundRequest.getLocationFrom(); - String itemCode = detail.getItem(); - String stockCode = detail.getLpn(); + //获取明细中所有的物料 + List itemCodes = detail.stream().map(InboundRequest.InboundDetail::getItem).toList(); - Point srcPoint = iPointService.validatePoint(srcPointCode); - Item item = itemService.validateItem(itemCode); + //获取存在的物料 + Map itemMap = itemService.queryByItemCodesToMap(itemCodes); + + //获取数据库不存在的物料集合且去重 + List notExistItemCodes = itemCodes.stream().filter(itemCode -> !itemMap.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); + } + + //托盘 + String stockCode = detail.get(0).getLpn(); Stock stock = iStockService.validateStock(stockCode); //获取输送线工作台点位,均衡分配点位任务-轮询方式 @@ -153,10 +179,15 @@ public class ISaiWmsServiceImpl implements ISaiWmsService { // 创建入库单和明细 Asn createAsn = buildAsn(inboundRequest); - AsnDetail asnDetail = buildAsnDetail(detail, srcPoint, dstPoint, item, stock); + List createAsnDetails = new ArrayList<>(); + for (InboundRequest.InboundDetail inboundDetail : inboundRequest.getDetails()) { + Item item = itemMap.get(inboundDetail.getItem()); + AsnDetail asnDetail = buildAsnDetail(inboundDetail, srcPoint, dstPoint, item, stock); + createAsnDetails.add(asnDetail); + } // 保存入库单和入库明细 - asnService.saveMain(createAsn, Collections.singletonList(asnDetail)); + asnService.saveMain(createAsn, createAsnDetails); //绑定容器和起点 iStockService.bindStock(stock, srcPoint); @@ -164,7 +195,8 @@ public class ISaiWmsServiceImpl implements ISaiWmsService { //成品入库需要生成AGV if (AsnOrderTypeEnum.PRODUCT.getValue().equals(createAsn.getOrderType())) { //创建AGV任务 - iAgvTaskService.createAgvTask(asnDetail.getId(), stock.getStockCode(), srcPoint.getPointCode(), dstPoint.getPointCode(), null, BusinessTypeEnum.INBOUND.getValue(), 0, AgvVendorEnum.HIK.getValue()); + String srcPointCode = srcPoint == null ? null : srcPoint.getPointCode(); + iAgvTaskService.createAgvTask(createAsn.getId(), stock.getStockCode(), srcPointCode, dstPoint.getPointCode(), null, BusinessTypeEnum.INBOUND.getValue(), 0, AgvVendorEnum.HIK.getValue()); } } @@ -320,7 +352,7 @@ public class ISaiWmsServiceImpl implements ISaiWmsService { .orderQty(BigDecimal.valueOf(detail.getQty())) .receivedQty(BigDecimal.ZERO) .stockId(stock.getId()) - .fromPointId(srcPoint.getId()) + .fromPointId(srcPoint == null ? null : srcPoint.getId()) .toPointId(dstPoint.getId()) .status(AsnStatusEnum.CREATED.getValue()) .project(detail.getProject()) diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/serialNumber/MoveSerialNumberRule.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/serialNumber/MoveSerialNumberRule.java new file mode 100644 index 0000000..fc089ec --- /dev/null +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/serialNumber/MoveSerialNumberRule.java @@ -0,0 +1,41 @@ +package org.cpte.modules.serialNumber; + +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.cpte.modules.utils.CodeGeneratorUtil; +import org.jeecg.common.handler.IFillRuleHandler; +import org.jeecg.modules.system.mapper.SysFillRuleMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class MoveSerialNumberRule implements IFillRuleHandler { + + @Autowired + private CodeGeneratorUtil codeGeneratorUtil; + + @Autowired + private SysFillRuleMapper sysFillRuleMapper; + + + @Override + public Object execute(JSONObject params, JSONObject formData) { + String prefix = params.getString("code"); + String code = codeGeneratorUtil.generateSerialNumber(prefix); + log.info("生成业务编号:{}", code); + return code; + } + + public String generateSerialNumber(String ruleCode) { + String ruleParams = sysFillRuleMapper.queryByRuleCode(ruleCode); + JSONObject jsonObject = JSONObject.parseObject(ruleParams); + String prefix = null; + if (jsonObject != null) { + prefix = jsonObject.getString("code"); + } else { + prefix = "MK"; + } + return codeGeneratorUtil.generateSerialNumber(prefix); + } +} 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 fa5be23a..a630944 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,11 +10,6 @@ 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.mapper.PickMapper; -import org.cpte.modules.shipping.mapper.TaskMapper; -import org.cpte.modules.shipping.service.ITaskService; -import org.jeecg.config.shiro.IgnoreAuth; import org.jeecgframework.poi.excel.ExcelImportUtil; import org.jeecgframework.poi.excel.def.NormalExcelConstants; import org.jeecgframework.poi.excel.entity.ExportParams; 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 e3617fc..e77c4f6 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 @@ -57,4 +57,12 @@ public interface ITaskService extends IService { */ void generateAgvTask(); + /** + * 根据需要移位的库位生成Task任务 + * + * @param movePointIds 库位 + * @return Task + */ + List bulidMoveTask(List movePointIds); + } 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 47616cd..ed2e5d2 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 @@ -4,8 +4,8 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.shiro.SecurityUtils; -import org.apache.shiro.lang.util.StringUtils; import org.cpte.modules.constant.GeneralConstant; import org.cpte.modules.base.entity.Item; import org.cpte.modules.base.entity.Point; @@ -14,7 +14,6 @@ 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.conveyorLine.vo.PointScore; import org.cpte.modules.inventory.entity.Inventory; import org.cpte.modules.inventory.mapper.InventoryMapper; import org.cpte.modules.inventory.service.IInventoryService; @@ -86,6 +85,7 @@ public class PickServiceImpl extends ServiceImpl implements IP @Autowired private BatchUtil batchUtils; + /** * 获取出库单Map * @@ -315,9 +315,9 @@ public class PickServiceImpl extends ServiceImpl implements IP Map itemMap = iItemService.queryByItemIdsToMap(itemIds); //筛选查询库存的条件(非空去重)批次、外部库存状态 、外部仓库 - List propC1List = pickDetails.stream().map(PickDetail::getPropC1).filter(StringUtils::hasText).distinct().toList(); - List propC3List = pickDetails.stream().map(PickDetail::getPropC3).filter(StringUtils::hasText).distinct().toList(); - List whCodeList = pickMap.values().stream().map(Pick::getWhCode).filter(StringUtils::hasText).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 whCodeList = pickMap.values().stream().map(Pick::getWhCode).filter(StringUtils::isNotBlank).distinct().toList(); //查询库存 List inventories = inventoryMapper.queryInventory(itemIds, propC1List, propC3List, whCodeList); @@ -376,7 +376,7 @@ public class PickServiceImpl extends ServiceImpl implements IP continue; } - //智能排序,优先分配移位最小的库位 + //智能排序,优先分配距离近、移位最少的库位 List scoredInventory = scoreInventories(matchedInventories); for (InventoryScore inventoryScore : scoredInventory) { @@ -416,9 +416,8 @@ public class PickServiceImpl extends ServiceImpl implements IP //移位任务 if (CollectionUtils.isNotEmpty(inventoryScore.getMovePoints())) { - for (Point movePoint : inventoryScore.getMovePoints()) { - log.info("生成移位任务:原库位:{}", movePoint.getPointCode()); - } + List movePointIds = inventoryScore.getMovePoints().stream().map(Point::getId).toList(); + createToTask.addAll(iTaskService.bulidMoveTask(movePointIds)); } else { log.info("无移位任务"); } @@ -486,7 +485,7 @@ public class PickServiceImpl extends ServiceImpl implements IP colLayerPointsMap.put(key, points); } - //获取所以出库口的库位 + //获取出库口的库位 List outPoints = iPointService.queryPoints(null, null, AreaTypeEnum.CK_DOCK.getValue()); //获取优化后的库存 @@ -497,7 +496,10 @@ public class PickServiceImpl extends ServiceImpl implements IP List points = colLayerPointsMap.get(key); return calculateMoveCount(inventory, currPoint, points, outPoints); }) - .sorted(Comparator.comparing(InventoryScore::getScore).reversed()) + //按分数倒序排序、移动次数升序排序 + .sorted(Comparator.comparing(InventoryScore::getScore).reversed() + .thenComparing(score -> score.getMovePoints().size()) + ) .toList(); } @@ -511,12 +513,14 @@ public class PickServiceImpl extends ServiceImpl implements IP * @return 库位位移次数 */ private InventoryScore calculateMoveCount(Inventory inventory, Point currPoint, List points, List outPoints) { - double totalScore = 0.0; + // 位移分数 + double moveScore; + //移位库位 + List movePoints; // 计算距离分数(权重30%) Point bestPoint = getBestOutboundPoint(currPoint, outPoints); - double distanceScore = calculateClusterDistanceCost(currPoint, bestPoint); - totalScore += distanceScore * 0.3; + double distanceScore = calculateClusterDistanceCost(currPoint, bestPoint) * 0.3; // 目标库位的深度位转换为索引 int targetIndex = Integer.parseInt(currPoint.getRowNum()) - 1; @@ -524,52 +528,46 @@ public class PickServiceImpl extends ServiceImpl implements IP //双通道 if (currPoint.getIzDoubleLane().equals(1)) { // 计算左侧占用数 - int leftOccupied = 0; - List leftPoints = new ArrayList<>(); - for (int i = 0; i < targetIndex; i++) { - Point point = points.get(i); - if (point != null && point.getStatus().equals(CommonStatusEnum.USED.getValue())) { - leftOccupied++; - leftPoints.add(point); - } - } + List leftPoints = calculateUsedPoints(points, 0, targetIndex); // 计算右侧占用数 - int rightOccupied = 0; - List rightPoints = new ArrayList<>(); - for (int i = targetIndex + 1; i < points.size(); i++) { - Point point = points.get(i); - if (point != null && point.getStatus().equals(CommonStatusEnum.USED.getValue())) { - rightOccupied++; - rightPoints.add(point); - } - } + List rightPoints = calculateUsedPoints(points, targetIndex + 1, points.size()); + //取两个集合中,元素最少的那个集合 - List movePoints = leftOccupied < rightOccupied ? leftPoints : rightPoints; - // 取最小值,移位越小分数越高 - int minOccupied = Math.min(leftOccupied, rightOccupied); - if (minOccupied == 0) { - minOccupied = 1; // 设置默认值防止除以0 - } - totalScore += 100.0 / minOccupied * 0.7; - return new InventoryScore(inventory, totalScore, bestPoint, movePoints); + movePoints = leftPoints.size() < rightPoints.size() ? leftPoints : rightPoints; + moveScore = (100.0 / (movePoints.size() + 1)) * 0.7; } else { - // 单通道,计算目标位置左侧的占用数 - int leftOccupied = 0; - List movePoints = new ArrayList<>(); - for (int i = 0; i < targetIndex; i++) { - Point point = points.get(i); - if (point != null && point.getStatus().equals(CommonStatusEnum.USED.getValue())) { - leftOccupied++; - movePoints.add(point); - } - } - if (leftOccupied == 0) { - leftOccupied = 1; - } - totalScore += 100.0 / leftOccupied * 0.7; - return new InventoryScore(inventory, totalScore, bestPoint, movePoints); + 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; } /** 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 b90764d..6bf64bd 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 @@ -8,11 +8,15 @@ 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.constant.enums.AgvVendorEnum; -import org.cpte.modules.constant.enums.BusinessTypeEnum; -import org.cpte.modules.constant.enums.InventoryStatusEnum; +import org.cpte.modules.base.mapper.PointMapper; +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.entity.Inventory; import org.cpte.modules.inventory.mapper.InventoryMapper; +import org.cpte.modules.serialNumber.MoveSerialNumberRule; import org.cpte.modules.shipping.entity.Task; import org.cpte.modules.shipping.mapper.TaskMapper; import org.cpte.modules.shipping.service.ITaskService; @@ -38,18 +42,33 @@ import java.util.stream.Collectors; @Slf4j public class TaskServiceImpl extends ServiceImpl implements ITaskService { + @Autowired + private PointMapper pointMapper; + @Autowired private TaskMapper taskMapper; @Autowired private InventoryMapper inventoryMapper; + @Autowired + private IItemService iItemService; + + @Autowired + private IPointService iPointService; + + @Autowired + private IStockService iStockService; + @Autowired private IAgvTaskService agvTaskService; @Autowired private BatchUtil batchUtils; + @Autowired + private MoveSerialNumberRule moveSerialNumberRule; + @Override @Transactional(rollbackFor = Exception.class) public Task createTask(String taskNo, Integer taskType, Item item, Point fromPoint, Point toPoint, Stock stock, Long pickId, Long pickDetailId, Long inventoryId, BigDecimal planQty, Integer izAll) { @@ -146,7 +165,7 @@ public class TaskServiceImpl extends ServiceImpl implements IT Integer izAll = allSplit ? 1 : 0; log.info("任务分组:{}", key); if (!groupToAgvTaskMap.containsKey(key)) { - AgvTask agvTask = agvTaskService.bulidAgvTask(null, key.getStockCode(), key.getFromPointCode(), key.getToPointCode(), null, BusinessTypeEnum.OUTBOUND.getValue(),izAll, AgvVendorEnum.TES.getValue()); + AgvTask agvTask = agvTaskService.bulidAgvTask(null, key.getStockCode(), key.getFromPointCode(), key.getToPointCode(), null, BusinessTypeEnum.OUTBOUND.getValue(), izAll, AgvVendorEnum.TES.getValue()); createToAgvTaskList.add(agvTask); groupToAgvTaskMap.put(key, agvTask); // 建立映射 log.info("创建AGV任务:{}", agvTask); @@ -180,4 +199,48 @@ public class TaskServiceImpl extends ServiceImpl implements IT } } + + @Override + public List bulidMoveTask(List movePointIds) { + List moveList = new ArrayList<>(); + List moveInventoryList = inventoryMapper.queryByPointIds(movePointIds); + + //移位物料 + List moveItemIds = moveInventoryList.stream().map(Inventory::getItemId).distinct().toList(); + Map moveItemMap = iItemService.queryByItemIdsToMap(moveItemIds); + + //移位库位 + List fromPointIds = moveInventoryList.stream().map(Inventory::getPointId).distinct().toList(); + Map fromPointMap = iPointService.queryByPointIdsToMap(fromPointIds); + + //移位容器 + List stockIds = moveInventoryList.stream().map(Inventory::getStockId).distinct().toList(); + Map stockMap = iStockService.queryByStockIdsToMap(stockIds); + + for (Inventory inv : moveInventoryList) { + + //判读改点位是否有出库任务 + Item moveItem = moveItemMap.get(inv.getItemId()); + Point fromPoint = fromPointMap.get(inv.getPointId()); + Stock stock = stockMap.get(inv.getStockId()); + 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); + moveList.add(moveTask); + log.info("生成移位任务:{}- 容器:{} - 库位:{} - 库存数量:{}", taskNo, stock.getStockCode(), fromPoint.getPointCode(), inv.getQuantity()); + inv.setStatus(InventoryStatusEnum.TRANSFER.getValue()); + inventoryMapper.updateById(inv); + } + return moveList; + } + + /** + * 选择最优移位目标 + */ + private Point findBestMovePoint(){ + // 1. 获取所有可用库位 + //List availablePoints = pointMapper.queryPoints(null, CommonStatusEnum.FREE.getValue(), areaCode); + return null; + } } 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 9f3eb68..d7a664e 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 @@ -229,10 +229,10 @@ public class ITesAgvServiceImpl implements ITesAgvService { * * @param agvTask 任务 */ - private void handleEnd(Long asnDetailId, AgvTask agvTask) { + private void handleEnd(Long asnId, AgvTask agvTask) { if (BusinessTypeEnum.INBOUND.getValue().equals(agvTask.getType())) { //收货 - iAsnService.receiveGoods(asnDetailId, agvTask.getEndCode()); + iAsnService.receiveGoods(asnId, agvTask.getEndCode()); } else if (BusinessTypeEnum.OUTBOUND.getValue().equals(agvTask.getType())) { //拣货 Point endPoint = iPointService.validatePoint(agvTask.getEndCode()); diff --git a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/ElevatorMapUtil.java b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/ElevatorMapUtil.java index ba726cf..147310c 100644 --- a/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/ElevatorMapUtil.java +++ b/cpte-boot-module/cpte-module-wms/src/main/java/org/cpte/modules/utils/ElevatorMapUtil.java @@ -19,11 +19,11 @@ public class ElevatorMapUtil { static { dataMap = new ConcurrentHashMap<>(); - List CKJBK01 = Arrays.asList("DT001", "DT002"); - List CKJBK02 = Arrays.asList("DT003", "DT004"); + List ST102 = Arrays.asList("DT001", "DT002"); + List ST104 = Arrays.asList("DT003", "DT004"); // 初始化Map - dataMap.put("CKJBK01", CKJBK01); - dataMap.put("CKJBK02", CKJBK02); + dataMap.put("ST102", ST102); + dataMap.put("ST104", ST104); } /** 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 57a1539..5cb843d 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 @@ -2,8 +2,8 @@ package org.cpte.modules.utils; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; -import java.util.HashMap; -import java.util.Map; +import java.text.SimpleDateFormat; +import java.util.*; public class test { public static void main(String[] args) { diff --git a/cpte-module-system/cpte-system-biz/src/main/java/org/jeecg/modules/system/entity/SysLog.java b/cpte-module-system/cpte-system-biz/src/main/java/org/jeecg/modules/system/entity/SysLog.java index 3b89d0a..9b00f27 100644 --- a/cpte-module-system/cpte-system-biz/src/main/java/org/jeecg/modules/system/entity/SysLog.java +++ b/cpte-module-system/cpte-system-biz/src/main/java/org/jeecg/modules/system/entity/SysLog.java @@ -88,6 +88,11 @@ public class SysLog implements Serializable { */ private String method; + /** + * 返回参数 + */ + private String returnData; + /** * 操作人用户名称 */ diff --git a/cpte-server-cloud/cpte-visual/cpte-cloud-test/zhongyou-cloud-test-shardingsphere/src/main/java/org/jeecg/modules/test/sharding/entity/ShardingSysLog.java b/cpte-server-cloud/cpte-visual/cpte-cloud-test/zhongyou-cloud-test-shardingsphere/src/main/java/org/jeecg/modules/test/sharding/entity/ShardingSysLog.java index c32cff2..cff3204 100644 --- a/cpte-server-cloud/cpte-visual/cpte-cloud-test/zhongyou-cloud-test-shardingsphere/src/main/java/org/jeecg/modules/test/sharding/entity/ShardingSysLog.java +++ b/cpte-server-cloud/cpte-visual/cpte-cloud-test/zhongyou-cloud-test-shardingsphere/src/main/java/org/jeecg/modules/test/sharding/entity/ShardingSysLog.java @@ -94,6 +94,9 @@ public class ShardingSysLog implements Serializable { */ private String logContent; + /** 返回 */ + private String returnData; + /** * 日志类型(1登录日志,2操作日志) */