gaoluyang
2026-11-03 a829dd34332bbc10c6b42ef86635ed48e34c6207
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;
@@ -430,7 +431,7 @@
        BigDecimal totalReturnQty = oldReturnQty.add(currentReturnQty);
        if (currentReturnQty.compareTo(BigDecimal.ZERO) > 0) {
            String returnBatchNo = resolveInventoryBatchNoFromStored(oldPick.getBatchNo());
            addInventory(oldPick.getId(), oldPick.getProductModelId(), returnBatchNo, currentReturnQty, FEED_RETURN_IN_RECORD_TYPE);
            addInventoryRecordOnly(oldPick.getId(), oldPick.getProductModelId(), returnBatchNo, currentReturnQty, FEED_RETURN_IN_RECORD_TYPE);
        }
        BigDecimal actualQty = defaultDecimal(oldPick.getQuantity())
@@ -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())
@@ -737,6 +778,31 @@
        }
    }
    private void addInventoryRecordOnly(Long recordId,
                                        Long productModelId,
                                        String batchNo,
                                        BigDecimal quantity,
                                        String stockInRecordType) {
        // 仅记录入库申请,不做审核通过。
        BigDecimal addQuantity = defaultDecimal(quantity);
        if (addQuantity.compareTo(BigDecimal.ZERO) <= 0) {
            return;
        }
        try {
            StockInventoryDto stockInventoryDto = new StockInventoryDto();
            stockInventoryDto.setProductModelId(productModelId);
            stockInventoryDto.setBatchNo(batchNo);
            stockInventoryDto.setQualitity(addQuantity);
            stockInventoryDto.setRecordType(stockInRecordType);
            stockInventoryDto.setRecordId(recordId == null ? 0L : recordId);
            stockInventoryService.addStockInRecordOnly(stockInventoryDto);
        } catch (ServiceException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new ServiceException("退料入库记录保存失败:" + ex.getMessage());
        }
    }
    private List<ProductionOrderPickDto> resolvePickItems(ProductionOrderPickDto dto) {
        // 解析新增场景的领料明细集合。
        if (dto == null) {