src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
@@ -31,6 +31,7 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -599,13 +600,53 @@ batchNoList = Collections.singletonList(null); } // 先查询各批次库存记录,并按首次入库时间升序排列(先进先出), // 不依赖前端勾选顺序,避免人工多选时扣错批次。 Map<String, StockInventory> inventoryMap = new LinkedHashMap<>(); for (String currentBatchNo : batchNoList) { inventoryMap.put(currentBatchNo, stockInventoryMapper.selectOne(buildStockWrapper(productModelId, currentBatchNo))); } // 首次入库时间以入库记录为准:历史数据批量导入时 stock_inventory.create_time 可能相同, // 不能准确反映批次先后。 Map<String, LocalDateTime> firstInTimeMap = new HashMap<>(); List<String> nonNullBatchNoList = batchNoList.stream() .filter(StringUtils::isNotEmpty) .collect(Collectors.toList()); if (!nonNullBatchNoList.isEmpty()) { List<StockInRecord> stockInRecords = stockInRecordService.list( Wrappers.<StockInRecord>lambdaQuery() .eq(StockInRecord::getProductModelId, productModelId) .in(StockInRecord::getBatchNo, nonNullBatchNoList)); for (StockInRecord stockInRecord : stockInRecords) { if (stockInRecord == null || stockInRecord.getBatchNo() == null || stockInRecord.getCreateTime() == null) { continue; } firstInTimeMap.merge(stockInRecord.getBatchNo(), stockInRecord.getCreateTime(), (a, b) -> a.isBefore(b) ? a : b); } } List<String> fifoBatchNoList = inventoryMap.entrySet().stream() .sorted(Comparator .comparing((Map.Entry<String, StockInventory> entry) -> firstInTimeMap.get(entry.getKey()), Comparator.nullsLast(Comparator.naturalOrder())) .thenComparing(entry -> entry.getValue() == null ? null : entry.getValue().getCreateTime(), Comparator.nullsLast(Comparator.naturalOrder())) .thenComparing(entry -> { StockInventory inventory = entry.getValue(); return inventory == null || inventory.getId() == null ? Long.MAX_VALUE : inventory.getId(); })) .map(Map.Entry::getKey) .collect(Collectors.toList()); // 先计算各批次可用量,避免边扣边算导致判断不一致。 Map<String, BigDecimal> availableQuantityMap = new LinkedHashMap<>(); BigDecimal totalAvailableQuantity = BigDecimal.ZERO; // 遍历批次,计算每个批次可用库存。 for (String currentBatchNo : batchNoList) { // 查询当前规格+批次的库存记录。 StockInventory stockInventory = stockInventoryMapper.selectOne(buildStockWrapper(productModelId, currentBatchNo)); // 按先进先出顺序计算每个批次可用库存。 for (String currentBatchNo : fifoBatchNoList) { StockInventory stockInventory = inventoryMap.get(currentBatchNo); BigDecimal availableQuantity = BigDecimal.ZERO; if (stockInventory != null) { availableQuantity = defaultDecimal(stockInventory.getQualitity()) src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -34,7 +34,9 @@ import com.ruoyi.production.service.ProductionOrderService; import com.ruoyi.sales.mapper.SalesLedgerMapper; import com.ruoyi.sales.mapper.SalesLedgerProductMapper; import com.ruoyi.stock.mapper.StockInRecordMapper; import com.ruoyi.stock.mapper.StockInventoryMapper; import com.ruoyi.stock.pojo.StockInRecord; import com.ruoyi.stock.pojo.StockInventory; import com.ruoyi.technology.mapper.*; import com.ruoyi.technology.pojo.*; @@ -70,6 +72,7 @@ private final QualityInspectFileMapper qualityInspectFileMapper; private final ProductionPlanMapper productionPlanMapper; private final StockInventoryMapper stockInventoryMapper; private final StockInRecordMapper stockInRecordMapper; private final StorageAttachmentMapper storageAttachmentMapper; private final StorageBlobMapper storageBlobMapper; private final SalesLedgerMapper salesLedgerMapper; @@ -1144,12 +1147,41 @@ List<StockInventory> stockList = stockInventoryMapper.selectList( Wrappers.<StockInventory>lambdaQuery() .in(StockInventory::getProductModelId, productModelIds)); // 各批次首次入库时间,用于先进先出排序(历史数据导入时库存行的 create_time 可能相同) Map<Long, Map<String, LocalDateTime>> firstInTimeMap = new HashMap<>(); List<StockInRecord> stockInRecords = stockInRecordMapper.selectList( Wrappers.<StockInRecord>lambdaQuery() .in(StockInRecord::getProductModelId, productModelIds) .isNotNull(StockInRecord::getBatchNo)); for (StockInRecord stockInRecord : stockInRecords) { if (stockInRecord == null || stockInRecord.getProductModelId() == null || stockInRecord.getBatchNo() == null || stockInRecord.getCreateTime() == null) { continue; } firstInTimeMap.computeIfAbsent(stockInRecord.getProductModelId(), key -> new HashMap<>()) .merge(stockInRecord.getBatchNo(), stockInRecord.getCreateTime(), (a, b) -> a.isBefore(b) ? a : b); } // 库存行按所属批次的首次入库时间升序,保证领料下拉框是先进先出顺序 stockList.sort(Comparator .comparing((StockInventory stockItem) -> { Map<String, LocalDateTime> modelBatchTimeMap = firstInTimeMap.get(stockItem.getProductModelId()); return modelBatchTimeMap == null ? null : modelBatchTimeMap.get(stockItem.getBatchNo()); }, Comparator.nullsLast(Comparator.naturalOrder())) .thenComparing(StockInventory::getCreateTime, Comparator.nullsLast(Comparator.naturalOrder())) .thenComparing(stockItem -> stockItem.getId() == null ? Long.MAX_VALUE : stockItem.getId())); for (StockInventory stockItem : stockList) { if (stockItem == null || stockItem.getProductModelId() == null) { continue; } Long productModelId = stockItem.getProductModelId(); stockQuantityMap.merge(productModelId, defaultDecimal(stockItem.getQualitity()), BigDecimal::add); // 已无可用库存(数量扣完或被锁定)的批次不再带到领料下拉框 BigDecimal availableQuantity = defaultDecimal(stockItem.getQualitity()) .subtract(defaultDecimal(stockItem.getLockedQuantity())); if (availableQuantity.compareTo(BigDecimal.ZERO) <= 0) { continue; } String batchNo = stockItem.getBatchNo(); if (batchNo != null && !batchNo.trim().isEmpty()) { stockBatchNoMap.computeIfAbsent(productModelId, key -> new LinkedHashSet<>()).add(batchNo); src/main/resources/mapper/basic/ProductModelMapper.xml
@@ -54,12 +54,25 @@ </select> <select id="selectBatchNoListByProductModelId" resultType="java.lang.String"> select distinct si.batch_no select si.batch_no from stock_inventory si left join ( select sir.batch_no, min(sir.create_time) as first_in_time from stock_in_record sir where sir.product_model_id = #{productModelId} and sir.batch_no is not null and sir.batch_no != '' group by sir.batch_no ) first_in on first_in.batch_no = si.batch_no where si.product_model_id = #{productModelId} and si.batch_no is not null and si.batch_no != '' order by si.batch_no and si.qualitity - IFNULL(si.locked_quantity, 0) > 0 group by si.batch_no order by MIN(first_in.first_in_time) is null, MIN(first_in.first_in_time), MIN(si.create_time), MIN(si.batch_no) </select> <select id="selectLatestRecord" resultType="com.ruoyi.basic.pojo.ProductModel"> SELECT * FROM product_model