no message

main
HUOJIN\92525 2025-11-16 20:23:29 +08:00
parent ba540b3f4c
commit f5304c1f31
28 changed files with 739 additions and 122 deletions

View File

@ -22,13 +22,4 @@ public interface AreaMapper extends BaseMapper<Area> {
*/
@Select("select * from base_area where area_code = #{areaCode}")
Area queryByAreaCode(@Param("areaCode") String areaCode);
/**
*
*
* @param areaCodes
* @return List<Item>
*/
@Select("select * from base_area where area_code in (#{areaCodes})")
List<Area> queryByAreaCodes(@Param("areaCodes") List<String> areaCodes);
}

View File

@ -23,13 +23,4 @@ public interface ItemMapper extends BaseMapper<Item> {
*/
@Select("select * from base_item where item_code = #{itemCode}")
Item queryByItemCode(@Param("itemCode") String itemCode);
/**
*
*
* @param itemCodes
* @return List<Item>
*/
@Select("select * from base_item where item_code in (#{itemCodes})")
List<Item> queryByItemCodes(@Param("itemCodes") List<String> itemCodes);
}

View File

@ -23,15 +23,6 @@ public interface PointMapper extends BaseMapper<Point> {
@Select("select * from base_point where point_code = #{pointCode}")
Point queryByPointCode(@Param("pointCode") String pointCode);
/**
*
*
* @param pointCodes
* @return List<Point>
*/
@Select("select * from base_point where point_code in (#{pointCodes})")
List<Point> queryByPointCodes(@Param("pointCodes") List<String> pointCodes);
/**
*
*

View File

@ -22,13 +22,4 @@ public interface StockMapper extends BaseMapper<Stock> {
*/
@Select("select * from base_stock where stock_code = #{stockCode}")
Stock queryByStockCode(@Param("stockCode") String stockCode);
/**
*
*
* @param stockCodes
* @return List<Stock>
*/
@Select("select * from base_stock where stock_code in (#{stockCodes})")
List<Stock> queryByStockCodes(@Param("stockCodes") List<String> stockCodes);
}

View File

@ -36,5 +36,12 @@ public interface IItemService extends IService<Item> {
*/
Map<String, Item> queryByItemCodesToMap(List<String> itemCodes);
/**
* ID
*
* @param itemIds ID
* @return Map<String, ItemEntity>
*/
Map<Long, Item> queryByItemIdsToMap(List<Long> itemIds);
}

View File

@ -34,4 +34,12 @@ public interface IPointService extends IService<Point> {
* @return Map<String, pointEntity>
*/
Map<String, Point> queryByPointCodesToMap(List<String> pointCodes);
/**
* ID
*
* @param pointIds
* @return Map<Long, pointEntity>
*/
Map<Long, Point> queryByPointIdsToMap(List<Long> pointIds);
}

View File

@ -37,4 +37,13 @@ public interface IStockService extends IService<Stock> {
*/
Map<String, Stock> queryByStockCodesToMap(List<String> stockCodes);
/**
* ID
*
* @param stockIds ID
* @return Map<Long, Stock>
*/
Map<Long, Stock> queryByStockIdsToMap(List<Long> stockIds);
}

View File

@ -55,7 +55,15 @@ public class ItemServiceImpl extends ServiceImpl<ItemMapper, Item> implements II
* @return List<ItemEntity>
*/
public List<Item> queryByItemCodes(List<String> itemCodes) {
return itemMapper.queryByItemCodes(itemCodes);
if (CollectionUtils.isEmpty(itemCodes)) {
return Collections.emptyList();
}
//查询物料
LambdaQueryWrapper<Item> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(Item::getItemCode, itemCodes);
queryWrapper.eq(Item::getDelFlag, 0);
queryWrapper.eq(Item::getIzActive, 1);
return itemMapper.selectList(queryWrapper);
}
/**
@ -77,4 +85,18 @@ public class ItemServiceImpl extends ServiceImpl<ItemMapper, Item> implements II
}
return itemMap;
}
@Override
public Map<Long, Item> queryByItemIdsToMap(List<Long> itemIds) {
if (CollectionUtils.isEmpty(itemIds)) {
return Collections.emptyMap();
}
List<Item> itemList = this.listByIds(itemIds);
//封装成map
Map<Long, Item> itemMap = Maps.newHashMap();
for (Item item : itemList) {
itemMap.put(item.getId(), item);
}
return itemMap;
}
}

View File

@ -6,6 +6,7 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.cpte.modules.base.entity.Area;
import org.cpte.modules.base.entity.Point;
import org.cpte.modules.base.entity.Stock;
import org.cpte.modules.base.mapper.AreaMapper;
import org.cpte.modules.base.mapper.PointMapper;
import org.cpte.modules.base.service.IAreaService;
@ -62,7 +63,15 @@ public class PointServiceImpl extends ServiceImpl<PointMapper, Point> implements
* @return List<PointEntity>
*/
public List<Point> queryByPointCodes(List<String> pointCodes) {
return pointMapper.queryByPointCodes(pointCodes);
if (CollectionUtils.isEmpty(pointCodes)) {
return Collections.emptyList();
}
//查询库位
LambdaQueryWrapper<Point> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(Point::getPointCode, pointCodes);
queryWrapper.eq(Point::getDelFlag, 0);
queryWrapper.eq(Point::getIzActive, 1);
return pointMapper.selectList(queryWrapper);
}
/**
@ -84,4 +93,17 @@ public class PointServiceImpl extends ServiceImpl<PointMapper, Point> implements
}
return PointMap;
}
@Override
public Map<Long, Point> queryByPointIdsToMap(List<Long> pointIds) {
if (CollectionUtils.isEmpty(pointIds)) {
return Collections.emptyMap();
}
List<Point> pointList = this.listByIds(pointIds);
Map<Long, Point> pointMap = Maps.newHashMap();
for (Point point : pointList) {
pointMap.put(point.getId(), point);
}
return pointMap;
}
}

View File

@ -59,7 +59,15 @@ public class StockServiceImpl extends ServiceImpl<StockMapper, Stock> implements
* @return List<StockEntity>
*/
public List<Stock> queryByStockCodes(List<String> stockCodes) {
return stockMapper.queryByStockCodes(stockCodes);
if (CollectionUtils.isEmpty(stockCodes)) {
return Collections.emptyList();
}
//查询容器
LambdaQueryWrapper<Stock> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(Stock::getStockCode, stockCodes);
queryWrapper.eq(Stock::getDelFlag, 0);
queryWrapper.eq(Stock::getIzActive, 1);
return stockMapper.selectList(queryWrapper);
}
/**
@ -81,4 +89,17 @@ public class StockServiceImpl extends ServiceImpl<StockMapper, Stock> implements
}
return stockMap;
}
@Override
public Map<Long, Stock> queryByStockIdsToMap(List<Long> stockIds) {
if (CollectionUtils.isEmpty(stockIds)) {
return Collections.emptyMap();
}
List<Stock> stockList = this.listByIds(stockIds);
Map<Long, Stock> stockMap = Maps.newHashMap();
for (Stock stock : stockList) {
stockMap.put(stock.getId(), stock);
}
return stockMap;
}
}

View File

@ -1,12 +1,13 @@
package org.cpte.modules.inventory.mapper;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.cpte.modules.inventory.entity.Inventory;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @Description:
* @author: cpte
@ -23,4 +24,16 @@ public interface InventoryMapper extends BaseMapper<Inventory> {
*/
@Select("select * from data_inventory where stock_id = #{stockId} and quantity>0 for update")
Inventory queryByStockId(@Param("stockId") Long stockId);
/**
*
*
* @param itemIds ID
* @param propC1List
* @param propC3List
* @param whCodeList
* @return List<Inventory>
*/
List<Inventory> queryInventoryWithLock(@Param("itemIds") List<Long> itemIds, @Param("propC1List") List<String> propC1List, @Param("propC3List") List<String> propC3List, @Param("whCodeList") List<String> whCodeList);
}

View File

@ -1,5 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.cpte.modules.inventory.mapper.InventoryMapper">
<select id="queryInventoryWithLock" resultType="org.cpte.modules.inventory.entity.Inventory">
SELECT * FROM data_inventory
WHERE status = 1
AND quantity > 0
AND item_id IN
<foreach collection="itemIds" item="itemId" open="(" separator="," close=")">
#{itemId}
</foreach>
<if test="propC1List != null and !propC1List.isEmpty()">
AND prop_c1 IN
<foreach collection="propC1List" item="propC1" open="(" separator="," close=")">
#{propC1}
</foreach>
</if>
<if test="propC3List != null and !propC3List.isEmpty()">
AND prop_c3 IN
<foreach collection="propC3List" item="propC3" open="(" separator="," close=")">
#{propC3}
</foreach>
</if>
<if test="whCodeList != null and !whCodeList.isEmpty()">
AND wh_code IN
<foreach collection="whCodeList" item="whCode" open="(" separator="," close=")">
#{whCode}
</foreach>
</if>
FOR UPDATE
</select>
</mapper>

View File

@ -7,6 +7,8 @@ import org.cpte.modules.receive.entity.AsnDetail;
import org.cpte.modules.receive.entity.ReceiveRecord;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
* @Description:

View File

@ -1,5 +1,9 @@
package org.cpte.modules.inventory.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.commons.collections4.CollectionUtils;
import org.cpte.modules.base.entity.Area;
import org.cpte.modules.base.mapper.AreaMapper;
import org.cpte.modules.constant.enums.InventoryStatusEnum;
import org.cpte.modules.inventory.entity.Inventory;
import org.cpte.modules.inventory.mapper.InventoryMapper;
@ -15,6 +19,8 @@ import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* @Description:
@ -24,6 +30,12 @@ import java.util.Date;
*/
@Service
public class InventoryServiceImpl extends ServiceImpl<InventoryMapper, Inventory> implements IInventoryService {
@Autowired
private AreaMapper areaMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Inventory createInventory(Long stockId, BigDecimal receivedQty, Asn asn, AsnDetail asnDetail, ReceiveRecord receiveRecord) {

View File

@ -62,11 +62,10 @@ public class InventoryLogServiceImpl extends ServiceImpl<InventoryLogMapper, Inv
@Override
@Transactional(rollbackFor = Exception.class)
public void addAllocInventoryLog(Inventory inventory, BigDecimal AllocatedQty, String businessNo, Long businessDetailId, String description) {
InventoryLog inventoryLog = buildInventoryLog(inventory, AllocatedQty, businessNo, businessDetailId, description);
InventoryLog inventoryLog = buildInventoryLog(inventory, BigDecimal.ZERO, businessNo, businessDetailId, description);
//出库分配
inventoryLog.setLogType(InventoryLogEnum.ALLOC.getValue());
//实际数量不变
inventoryLog.setChangeQty(BigDecimal.ZERO);
inventoryLog.setBeforeAllocatedQty(BigDecimalUtil.subtract(inventory.getQueuedQty(), AllocatedQty, 0));
inventoryLog.setAfterAllocatedQty(inventory.getQueuedQty());
addInventoryLog(inventoryLog);

View File

@ -0,0 +1,84 @@
package org.cpte.modules.quartz.job;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.cpte.modules.shipping.entity.PickDetail;
import org.cpte.modules.shipping.mapper.PickDetailMapper;
import org.cpte.modules.shipping.service.IPickService;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.modules.base.service.BaseCommonService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
*
*/
@Slf4j
public class AllocateJob implements Job {
@Autowired
private PickDetailMapper pickDetailMapper;
@Autowired
private IPickService iPickService;
@Autowired
private BaseCommonService baseCommonService;
// 使用 ConcurrentHashMap 缓存已处理的结果
private static final Map<String, Boolean> processedCache = new ConcurrentHashMap<>();
// 缓存最大大小,防止内存溢出
private static final int MAX_CACHE_SIZE = 1000;
@Override
public void execute(JobExecutionContext jobExecutionContext) {
// 查询未分配或者部分分配的出库明细
List<PickDetail> pickDetailList = pickDetailMapper.queryUnallocatedPickDetail();
if (pickDetailList.isEmpty()) {
return;
}
// 分配出库明细
long startTime = System.currentTimeMillis();
List<String> resultMsg = iPickService.allocatePickDetail2(pickDetailList);
long endTime = System.currentTimeMillis();
log.info("分配出库明细耗时:{}ms", endTime - startTime);
if (CollectionUtils.isNotEmpty(resultMsg)) {
// 生成缓存键
String cacheKey = generateCacheKey(resultMsg);
// 检查是否已经处理过相同的内容
if (!processedCache.containsKey(cacheKey)) {
// 新内容,记录日志
baseCommonService.addLog("出库任务分配:" + "\n" + cacheKey, CommonConstant.LOG_TYPE_2, CommonConstant.OPERATE_TYPE_1);
// 添加到缓存
processedCache.put(cacheKey, true);
// 控制缓存大小
if (processedCache.size() > MAX_CACHE_SIZE) {
// 移除一部分旧缓存(简单实现,可以按需优化)
processedCache.clear();
}
}
}
}
/**
* -
*/
private String generateCacheKey(List<String> resultMsg) {
// 对结果进行排序后拼接,确保相同内容生成相同键
return resultMsg.stream()
.filter(Objects::nonNull)
.sorted()
.collect(Collectors.joining("\n"));
}
}

View File

@ -26,6 +26,9 @@ public class TesAgvJob implements Job {
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 查询待执行任务
List<AgvTask> agvTaskList = agvTaskMapper.queryAgvTaskList(AgvStatusEnum.CREATED.getValue(), AgvVendorEnum.TES.getValue());
if (agvTaskList.isEmpty()) {
return;
}
String taskSubmitUrl = "http://localhost:8000/cpte-wms/tes/apiv2/newMovePodTask";
for (AgvTask agvTask : agvTaskList) {
try {

View File

@ -42,4 +42,7 @@ public interface AsnDetailMapper extends BaseMapper<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);
@Select("select MAX(line_no) from data_asn_detail where asn_id = #{asnId} ")
Integer queryMaxLineNoByAsnId(@Param("asnId") Long asnId);
}

View File

@ -5,11 +5,9 @@ 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.IStockService;
import org.cpte.modules.constant.enums.AsnStatusEnum;
import org.cpte.modules.constant.enums.CommonStatusEnum;
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;
@ -46,7 +44,6 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
private StockMapper stockMapper;
@Autowired
private PointMapper pointMapper;
@Autowired
private AsnMapper asnMapper;
@Autowired
@ -62,15 +59,22 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
@Transactional(rollbackFor = Exception.class)
public void saveMain(Asn asn, List<AsnDetail> asnDetailList) {
asnMapper.insert(asn);
if (asnDetailList != null && !asnDetailList.isEmpty()) {
AtomicInteger lineNoCounter = new AtomicInteger(1);
for (AsnDetail entity : asnDetailList) {
if (entity.getLineNo() == null || entity.getLineNo() == 0) {
entity.setLineNo(lineNoCounter.getAndIncrement());
}
entity.setAsnId(asn.getId());
asnDetailMapper.insert(entity);
if (asnDetailList == null || asnDetailList.isEmpty()) {
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) {
entity.setLineNo(lineNoCounter.getAndIncrement());
}
entity.setAsnId(asn.getId());
asnDetailMapper.insert(entity);
}
//刷新入库单
refreshAsn(asn, asnDetailList);
@ -114,20 +118,28 @@ public class AsnServiceImpl extends ServiceImpl<AsnMapper, Asn> implements IAsnS
@Override
@Transactional(rollbackFor = Exception.class)
public void updateMain(Asn asn, List<AsnDetail> asnDetailList) {
// 直接更新主表
asnMapper.updateById(asn);
// 更新明细表 - 只更新传入的明细数据
if (asnDetailList != null && !asnDetailList.isEmpty()) {
for (AsnDetail entity : asnDetailList) {
entity.setAsnId(asn.getId());
// 直接更新,而不是删除后重新插入
if (entity.getId() != null) {
asnDetailMapper.updateById(entity);
}
}
if (asnDetailList == null || asnDetailList.isEmpty()) {
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) {
entity.setLineNo(lineNoCounter.getAndIncrement());
}
entity.setAsnId(asn.getId());
if (entity.getId() != null) {
asnDetailMapper.updateById(entity);
} else {
asnDetailMapper.insert(entity);
}
}
// 刷新入库单状态
refreshAsn(asn, asnDetailList);

View File

@ -36,7 +36,7 @@ public class SaiWmsController {
@IgnoreAuth
public Result<String> inBoundTask(@RequestBody @Valid InboundRequest inboundRequest) {
iSaiWmsService.inBoundTask(inboundRequest);
return Result.OK("下发成功!");
return Result.OK("操作成功!");
}
/**
@ -50,6 +50,6 @@ public class SaiWmsController {
@IgnoreAuth
public Result<String> outBoundTask(@RequestBody @Valid OutboundRequest outboundRequest) {
iSaiWmsService.outBoundTask(outboundRequest);
return Result.OK("下发成功!");
return Result.OK("操作成功!");
}
}

View File

@ -39,7 +39,6 @@ public class OutboundRequest {
private Integer type;
// 生产车间
@NotBlank(message = "生产车间")
@JsonProperty("Enterprise")
private String enterprise;

View File

@ -140,7 +140,7 @@ public class ISaiWmsServiceImpl implements ISaiWmsService {
//获取不存在的物料
List<String> notExitItemCodes = itemCodes.stream().filter(itemCode -> !exitItemMap.containsKey(itemCode)).toList();
if (!notExitItemCodes.isEmpty()) {
throw new RuntimeException("系统无" + notExitItemCodes + "物料,请维护");
throw new RuntimeException("系统无" + notExitItemCodes + "物料,请维护");
}
// 创建出库单和明细

View File

@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToEmptyObjectSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@ -1,6 +1,8 @@
package org.cpte.modules.shipping.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import org.cpte.modules.shipping.entity.PickDetail;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
@ -28,4 +30,12 @@ public interface PickDetailMapper extends BaseMapper<PickDetail> {
* @return List<PickDetail>
*/
public List<PickDetail> selectByMainId(@Param("mainId") Long mainId);
@Select("select * from data_pick_detail where status = 1 " +
"union all " +
"select * from data_pick_detail where status = 2 ")
List<PickDetail> queryUnallocatedPickDetail();
@Select("select MAX(line_no) from data_pick_detail where pick_id = #{pickId} ")
Integer queryMaxLineNoByPickId(@Param("pickId") Long pickId);
}

View File

@ -3,6 +3,7 @@ package org.cpte.modules.shipping.service;
import org.cpte.modules.shipping.entity.PickDetail;
import org.cpte.modules.shipping.entity.Pick;
import com.baomidou.mybatisplus.extension.service.IService;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
@ -10,39 +11,53 @@ import java.util.List;
/**
* @Description:
* @author: cpte
* @Date: 2025-11-14
* @Date: 2025-11-14
* @Version: V1.0
*/
public interface IPickService extends IService<Pick> {
/**
*
*
* @param pick
* @param pickDetailList
*/
public void saveMain(Pick pick,List<PickDetail> pickDetailList) ;
/**
*
*
* @param pick
* @param pickDetailList
*/
public void updateMain(Pick pick,List<PickDetail> pickDetailList);
/**
*
*
* @param id
*/
public void delMain (Long id);
/**
*
*
* @param idList
*/
public void delBatchMain (Collection<? extends Serializable> idList);
/**
*
*
* @param pick
* @param pickDetailList
*/
public void saveMain(Pick pick, List<PickDetail> pickDetailList);
/**
*
*
* @param pick
* @param pickDetailList
*/
public void updateMain(Pick pick, List<PickDetail> pickDetailList);
/**
*
*
* @param id
*/
public void delMain(Long id);
/**
*
*
* @param idList
*/
public void delBatchMain(Collection<? extends Serializable> idList);
/**
*
*
* @param pickDetails
*/
List<String> allocatePickDetail(List<PickDetail> pickDetails);
/**
*
*
* @param pickDetails
*/
List<String> allocatePickDetail2(List<PickDetail> pickDetails);
}

View File

@ -1,15 +1,30 @@
package org.cpte.modules.shipping.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.shiro.lang.util.StringUtils;
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.AsnStatusEnum;
import org.cpte.modules.constant.enums.InventoryStatusEnum;
import org.cpte.modules.constant.enums.PickStatusEnum;
import org.cpte.modules.receive.entity.Asn;
import org.cpte.modules.receive.entity.AsnDetail;
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.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.IPickService;
import org.cpte.modules.shipping.vo.InventoryGroupKey;
import org.cpte.modules.utils.BigDecimalUtil;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.modules.base.service.BaseCommonService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
@ -17,10 +32,9 @@ import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* @Description:
@ -29,31 +43,72 @@ import java.util.concurrent.atomic.AtomicInteger;
* @Version: V1.0
*/
@Service
@Slf4j
public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IPickService {
@Autowired
private PickMapper pickMapper;
@Autowired
private PickDetailMapper pickDetailMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private IItemService iItemService;
@Autowired
private IStockService iStockService;
@Autowired
private IPointService iPointService;
@Autowired
private IInventoryLogService iInventoryLogService;
@Autowired
private BaseCommonService baseCommonService;
/**
* Map
*
* @param pickIds id
* @return Map
*/
private Map<Long, Pick> queryByPickIdsToMap(List<Long> pickIds) {
Map<Long, Pick> pickMap = new HashMap<>();
List<Pick> pickList = pickMapper.selectByIds(pickIds);
for (Pick pick : pickList) {
pickMap.put(pick.getId(), pick);
}
return pickMap;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveMain(Pick pick, List<PickDetail> pickDetailList) {
pickMapper.insert(pick);
if (pickDetailList != null && !pickDetailList.isEmpty()) {
AtomicInteger lineNoCounter = new AtomicInteger(1);
for (PickDetail entity : pickDetailList) {
if (entity.getLineNo() == null || entity.getLineNo() == 0) {
entity.setLineNo(lineNoCounter.getAndIncrement());
}
entity.setPickId(pick.getId());
pickDetailMapper.insert(entity);
if (pickDetailList == null || pickDetailList.isEmpty()) {
throw new RuntimeException("请新增出库明细");
}
// 获取当前出库单下已存在的最大序号
Integer maxLineNo = pickDetailMapper.queryMaxLineNoByPickId(pick.getId());
AtomicInteger lineNoCounter = new AtomicInteger((maxLineNo != null) ? maxLineNo + 1 : 1);
for (PickDetail entity : pickDetailList) {
if (entity.getLineNo() == null || entity.getLineNo() == 0) {
entity.setLineNo(lineNoCounter.getAndIncrement());
}
entity.setPickId(pick.getId());
pickDetailMapper.insert(entity);
}
//刷新出库单
refreshPick(pick, pickDetailList);
}
/**
*
*
* @param pick
* @param pickDetails
*/
public synchronized void refreshPick(Pick pick, List<PickDetail> pickDetails) {
if (pickDetails == null) {
pickDetails = new ArrayList<>();
@ -103,17 +158,41 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
@Override
@Transactional(rollbackFor = Exception.class)
public void updateMain(Pick pick, List<PickDetail> pickDetailList) {
// 直接更新主表
pickMapper.updateById(pick);
// 更新明细表 - 只更新传入的明细数据
if (pickDetailList != null && !pickDetailList.isEmpty()) {
for (PickDetail entity : pickDetailList) {
entity.setPickId(pick.getId());
// 直接更新,而不是删除后重新插入
if (entity.getId() != null) {
pickDetailMapper.updateById(entity);
}
if (pickDetailList == null || pickDetailList.isEmpty()) {
throw new RuntimeException("请新增出库明细");
}
// 删除多余的出库明细
List<PickDetail> exitPickDetailList = pickDetailMapper.selectByMainId(pick.getId());
if (exitPickDetailList.size() > pickDetailList.size()) {
List<Long> pickDetailIds = pickDetailList.stream()
.map(PickDetail::getId)
.toList();
//两个集合的差集id 需要删除
List<Long> deleteIds = exitPickDetailList.stream()
.filter(e -> e.getId() != null && !pickDetailIds.contains(e.getId()))
.map(PickDetail::getId)
.toList();
pickDetailMapper.deleteByIds(deleteIds);
//添加删除日志
baseCommonService.addLog(pick.getNo() + "任务号,删除明细:" + deleteIds, CommonConstant.LOG_TYPE_2, CommonConstant.OPERATE_TYPE_4);
}
// 获取当前出库单下已存在的最大序号
Integer maxLineNo = pickDetailMapper.queryMaxLineNoByPickId(pick.getId());
AtomicInteger lineNoCounter = new AtomicInteger((maxLineNo != null) ? maxLineNo + 1 : 1);
for (PickDetail entity : pickDetailList) {
if (entity.getLineNo() == null || entity.getLineNo() == 0) {
entity.setLineNo(lineNoCounter.getAndIncrement());
}
entity.setPickId(pick.getId());
if (entity.getId() != null) {
pickDetailMapper.updateById(entity);
} else {
pickDetailMapper.insert(entity);
}
}
@ -150,4 +229,282 @@ public class PickServiceImpl extends ServiceImpl<PickMapper, Pick> implements IP
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public List<String> allocatePickDetail(List<PickDetail> pickDetails) {
List<String> errorMsgList = new ArrayList<>();
//查询出库单
List<Long> pickIds = pickDetails.stream().map(PickDetail::getPickId).distinct().toList();
Map<Long, Pick> pickMap = queryByPickIdsToMap(pickIds);
//获取不为null、空字符的批次号
List<String> propC1List = pickDetails.stream().map(PickDetail::getPropC1).filter(Objects::nonNull).filter(e -> !e.isEmpty()).distinct().toList();
//获取不为null、空字符的外部库存状态
List<String> propC3List = pickDetails.stream().map(PickDetail::getPropC3).filter(Objects::nonNull).filter(e -> !e.isEmpty()).distinct().toList();
//获取不为null、空字符的仓库代码
List<String> whCodeList = pickMap.values().stream().map(Pick::getWhCode).filter(Objects::nonNull).filter(e -> !e.isEmpty()).distinct().toList();
//查询需要分配的物料
List<Long> itemIds = pickDetails.stream().map(PickDetail::getItemId).distinct().toList();
Map<Long, Item> itemMap = iItemService.queryByItemIdsToMap(itemIds);
//根据物料、批次号、外部库存状态、外部仓库代码批量查询库存
List<Inventory> inventories = inventoryMapper.queryInventoryWithLock(itemIds, propC1List, propC3List, whCodeList);
if (inventories.isEmpty()) {
List<String> itemCodes = itemMap.values().stream().map(Item::getItemCode).toList();
errorMsgList.add(itemCodes + "物料库存不足");
return errorMsgList;
}
//容器
List<Long> stockIds = inventories.stream().map(Inventory::getStockId).distinct().toList();
Map<Long, Stock> stockMap = iStockService.queryByStockIdsToMap(stockIds);
//目标库位
List<Long> pointIds = inventories.stream().map(Inventory::getPointId).distinct().toList();
Map<Long, Point> pointMap = iPointService.queryByPointIdsToMap(pointIds);
List<Inventory> updateToInventory = new ArrayList<>();
List<PickDetail> updateToPickDetail = new ArrayList<>();
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;
}
for (Inventory inventory : inventories) {
// 筛选符合要求的库存记录
if (!inventory.getItemId().equals(pickDetail.getItemId())) {
continue;
}
// 匹配批次号(如果有指定)
if (pickDetail.getPropC1() != null && !pickDetail.getPropC1().isEmpty()) {
if (!pickDetail.getPropC1().equals(inventory.getPropC1())) {
continue;
}
}
// 匹配库存状态(如果有指定)
if (pickDetail.getPropC3() != null && !pickDetail.getPropC3().isEmpty()) {
if (!pickDetail.getPropC3().equals(inventory.getPropC3())) {
continue;
}
}
// 匹配仓库
if (!pick.getWhCode().equals(inventory.getWhCode())) {
continue;
}
//库存可用数量
BigDecimal availableQty = BigDecimalUtil.subtract(inventory.getQuantity(), inventory.getQueuedQty(), 0);
if (availableQty.compareTo(BigDecimal.ZERO) <= 0) {
continue;
}
BigDecimal allocateQty = unAllocatedQty.compareTo(availableQty) < 0 ? unAllocatedQty : 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);
// 更新明细状态
if (fpQty.compareTo(pickDetail.getOrderQty()) >= 0) {
pickDetail.setStatus(PickStatusEnum.ASSIGNED.getValue());
} else {
pickDetail.setStatus(PickStatusEnum.PARTIAL.getValue());
}
updateToPickDetail.add(pickDetail);
//容器
Stock stock = stockMap.get(inventory.getStockId());
//库位
Point point = pointMap.get(inventory.getPointId());
//TODO 后续补充生成Task任务
//判断是否整托
String tuo = "拆托";
if (inventory.getQuantity().compareTo(allocateQty) == 0) {
tuo = "整托";
}
log.info("生成任务: {} - {} - {}", stock.getStockCode(), point.getPointCode(), tuo);
unAllocatedQty = BigDecimalUtil.subtract(unAllocatedQty, allocateQty, 0);
//分配库存日志
iInventoryLogService.addAllocInventoryLog(inventory, allocateQty, pick.getOrderNo(), pickDetail.getId(), pickDetail.getDescription());
// 如果已完全分配,则跳出内层循环
if (unAllocatedQty.compareTo(BigDecimal.ZERO) <= 0) {
break;
}
}
// 若仍有未分配的数量,说明库存不足
if (unAllocatedQty.compareTo(BigDecimal.ZERO) > 0) {
errorMsgList.add(item.getItemCode() + "物料库存不足");
continue;
}
}
//批量操作
if (CollectionUtils.isNotEmpty(updateToInventory)) {
inventoryMapper.updateById(updateToInventory);
}
if (CollectionUtils.isNotEmpty(updateToPickDetail)) {
pickDetailMapper.updateById(updateToPickDetail);
}
//刷新出库单
for (Pick pick : pickMap.values()) {
refreshPick(pick, pickDetailMapper.selectByMainId(pick.getId()));
}
return errorMsgList;
}
@Override
@Transactional(rollbackFor = Exception.class)
public List<String> allocatePickDetail2(List<PickDetail> pickDetails) {
// 错误信息去重LinkedHashSet保证顺序和唯一
Set<String> errorMsgSet = new LinkedHashSet<>();
// -------------------------- 1. 查询关联数据批量查询减少DB交互--------------------------
//获取出库单
List<Long> pickIds = pickDetails.stream().map(PickDetail::getPickId).distinct().toList();
Map<Long, Pick> pickMap = queryByPickIdsToMap(pickIds);
//获取物料
List<Long> itemIds = pickDetails.stream().map(PickDetail::getItemId).distinct().toList();
Map<Long, Item> itemMap = iItemService.queryByItemIdsToMap(itemIds);
//筛选查询库存的条件(非空去重)批次、外部库存状态 、外部仓库
List<String> propC1List = pickDetails.stream().map(PickDetail::getPropC1).filter(StringUtils::hasText).distinct().toList();
List<String> propC3List = pickDetails.stream().map(PickDetail::getPropC3).filter(StringUtils::hasText).distinct().toList();
List<String> whCodeList = pickMap.values().stream().map(Pick::getWhCode).filter(StringUtils::hasText).distinct().toList();
//查询库存
List<Inventory> inventories = inventoryMapper.queryInventoryWithLock(itemIds, propC1List, propC3List, whCodeList);
if (CollectionUtils.isEmpty(inventories)) {
String itemCodes = itemMap.values().stream().map(Item::getItemCode).collect(Collectors.joining(","));
errorMsgSet.add("【" + itemCodes + "】物料库存不足");
return new ArrayList<>(errorMsgSet);
}
//获取容器
List<Long> stockIds = inventories.stream().map(Inventory::getStockId).distinct().toList();
Map<Long, Stock> stockMap = iStockService.queryByStockIdsToMap(stockIds);
//获取目标库位
List<Long> pointIds = inventories.stream().map(Inventory::getPointId).distinct().toList();
Map<Long, Point> pointMap = iPointService.queryByPointIdsToMap(pointIds);
// -------------------------- 2. 库存分组排序(优化匹配效率)--------------------------
// 按「物料ID+批次+库存状态+仓库」分组且按创建时间升序FIFO分配规则
Map<InventoryGroupKey, List<Inventory>> inventoryGroupMap = inventories.stream()
.sorted(Comparator.comparing(Inventory::getCreateTime))
.collect(Collectors.groupingBy(inv -> InventoryGroupKey.of(
inv.getItemId(),
inv.getPropC1(),
inv.getPropC3(),
inv.getWhCode()
)));
// -------------------------- 3. 创建更新列表---------------------------
List<Inventory> updateToInventory = new ArrayList<>();
List<PickDetail> updateToPickDetail = 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(),
pickDetail.getPropC1(),
pickDetail.getPropC3(),
pick.getWhCode()
);
List<Inventory> matchedInventories = inventoryGroupMap.getOrDefault(groupKey, Collections.emptyList());
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;
}
for (Inventory inventory : matchedInventories) {
if (unAllocatedQty.compareTo(BigDecimal.ZERO) <= 0) {
break;
}
// 库存可用数量
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 point = pointMap.get(inventory.getPointId());
String tuoType = inventory.getQuantity().compareTo(allocateQty) == 0 ? "整托" : "拆托";
log.info("生成拣货任务: 容器:{} - 库位:{} - 类型:{}- 分配数量:{}", stock.getStockCode(), point.getPointCode(), tuoType, allocateQty);
//分配库存日志
iInventoryLogService.addAllocInventoryLog(inventory, allocateQty, pick.getOrderNo(), pickDetail.getId(), pickDetail.getDescription());
//更新未分配数量
unAllocatedQty = BigDecimalUtil.subtract(unAllocatedQty, allocateQty, 0);
}
//分配后仍有缺货,记录错误
if (unAllocatedQty.compareTo(BigDecimal.ZERO) > 0) {
errorMsgSet.add(String.format("物料【%s】库存不足需求:%s已分配:%s缺货:%s",
item.getItemCode(), pickDetail.getOrderQty(),
BigDecimalUtil.subtract(pickDetail.getOrderQty(), unAllocatedQty, 0),
unAllocatedQty));
}
}
// -------------------------- 5. 批量更新减少DB交互--------------------------
if (CollectionUtils.isNotEmpty(updateToInventory)) {
inventoryMapper.updateById(updateToInventory);
}
if (CollectionUtils.isNotEmpty(updateToPickDetail)) {
pickDetailMapper.updateById(updateToPickDetail);
}
// -------------------------- 6. 刷新出库单状态 --------------------------
for (Pick pick : pickMap.values()) {
refreshPick(pick, pickDetailMapper.selectByMainId(pick.getId()));
}
return new ArrayList<>(errorMsgSet);
}
}

View File

@ -0,0 +1,27 @@
package org.cpte.modules.shipping.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class InventoryGroupKey {
private Long itemId;
private String propC1; // 批次号
private String propC3; // 库存状态
private String whCode; // 仓库代码
public static InventoryGroupKey of(Long itemId, String propC1, String propC3, String whCode) {
return new InventoryGroupKey(
itemId,
StringUtils.defaultIfBlank(propC1, ""), // 空值统一为"",避免分组不一致
StringUtils.defaultIfBlank(propC3, ""),
StringUtils.defaultIfBlank(whCode, "")
);
}
}

View File

@ -64,7 +64,7 @@ public class SysLogController extends JeecgController<SysLog, ISysLogService> {
//日志关键词
String keyWord = req.getParameter("keyWord");
if(oConvertUtils.isNotEmpty(keyWord)) {
queryWrapper.like("log_content",keyWord);
queryWrapper.like("log_content",keyWord).or().like("request_param",keyWord);
}
//TODO 过滤逻辑处理
//TODO begin、end逻辑处理